NBA Top Shot已经向其80万用户售卖了超过3亿美元的NFT,索斯比则刚刚以1700万美元的价格拍出 一幅数字艺术品。当你购买一个NBA Top Shot藏品时,你并没有获得唯一的商业权力,你甚至不能独享其权力。 实际上那幅1700万美元的艺术品,你可以免费观赏。
不过让我们探讨下NFT带来的价值:资产具有密码学算法可验证的所有权以及合约赋予的售卖或转让能力。
很多类型的资产可以受益于密码学可验证的所有权:成绩单、证书、知识产权等。想象一下用 区块链代替USPTO… 可以跳过律师直接提交你的申请,先到先服务。
是一个朋友的建议让我看是考虑将门票作为一种NFT资产,这很有意义。票务市场最大的问题 是什么?下面是一些:
- 防伪
- 大量的交易手续费。我们都知道当你看到50美元的门票却需要花费72.50美元以便覆盖售票 中介的成本时的感觉
- 不受监管的二级市场
如果我们使用智能合约来管理资产,这些问题就会消失。确定NFT的真实性是小事一桩,交易 手续费也可以通过采纳区块链得到大幅削减。
对我而言,最令人兴奋的一点是票务发行人能够设置二级市场条款。你可以让你的资产不可 转让,确保只能低于标价出售,甚至在任何二次销售时帮助表演者削减成本。
在这个去中心化系统中,每个人都可以得到更公平透明的体验。好了,让我们开始这个系统的开发。
1、门票NFT的区块链平台选择:以太坊 vs. Flow
我们需要做出的第一个决定,是选择使用哪个区块链平台。
我们可以使用以太坊,但是交易手续费有点高,虽然在未来的ETH 2.0升级后手续费可能 显著下降。
Flow是一个开发者友好的新型区块链生态,手续费微乎其微,听起来是一个好的选择。
实际上Dapper Lab的NBA Top Shot就是使用Flow智能合约,和我们下面要部署的合约没有太多区别。
从较高的层面讲,下面就是我们需要构造的一个基本但可用的票据集市,我们没有实现完整 的买/卖功能,不过这可能是下一个教程的主题。
- 在我们的Flow智能合约中定义票证的不可转让条款
- 创建虚拟账号以便发行人和参与者能够访问NFT
- 使用交易来安全地展示常见功能,例如铸造和票证转让
- 使用React.js实现一个简单的前端web界面
2、Flow区块链开发环境设置
我利用Flow文档中的教程来熟悉其智能合约编程语言Cadence以及其标准的NFT模板。 如果你打算遵循这一学习路径,则需要安装Flow CLI。
当然我们可以在Flow主网或测试网部署合约,但是我们讲利用FLow仿真器来进行快速本地开发。 使用如下命令启动仿真器:
3、Flow区块链门票NFT智能合约开发
我们的非同质化票证智能合约需要定义NFT的特点以及铸造、存储、转让等函数。其中某些 功能需要公开可用,例如存入或获取元数据,而另一些功能例如提取和铸造,则需要一定的 权限才可以执行。
我也希望确认我们的票证是不可转让的,因此我们需要设置必要的检查条件以便禁止多次存入。 下面看一下我们的Cadence智能合约。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
// NonFungibleTicket.cdc // contract to manage unique tradeable tickets (based on Flow NFT standard) // see: https://docs.onflow.org/cadence/tutorial/04-non-fungible-tokens/
pub contract NonFungibleTicket {
// set up a couple events for key deposits/withdrawals pub event Withdraw(id: UInt64, from: Address?) pub event Deposit(id: UInt64, to: Address?)
// our NFT (Non-Fungible-Ticket) is simply defined by an id. metadata coming later pub resource NFT { pub let id: UInt64 // we want to make our ticket non-transferrable, so let's keep track of how many times it has changed hands pub var numTransfers : UInt64
init(initID: UInt64) { self.id = initID self.numTransfers = 0 } // we will need a function to iterate the number of transfers each time pub fun transfer() { self.numTransfers = self.numTransfers + 1 as UInt64 } }
// receiver interface allows others to interact w certain functions via public access pub resource interface NFTReceiver { pub fun deposit(token: @NFT, metadata: {String : String}) pub fun getIDs(): [UInt64] pub fun idExists(id: UInt64): Bool pub fun getMetadata(id: UInt64) : {String : String} // obviously, we don't allow public access to withdraw/minting functions }
// defining a Collection resource for all our tickets pub resource Collection: NFTReceiver { pub var ownedNFTs: @{UInt64: NFT} pub var metadataObjs: {UInt64: { String : String }}
init () { self.ownedNFTs <- {} self.metadataObjs = {} }
// withdraw forced to be non-nil. function throws error if NFT with withdrawID doesn't exist pub fun withdraw(withdrawID: UInt64): @NFT { let token <- self.ownedNFTs.remove(key: withdrawID)!
emit Withdraw(id: token.id, from: self.owner?.address)
return <- token }
// the deposit function is a bit more complex. first of all, this is where the metadata comes in: pub fun deposit(token: @NFT, metadata: {String : String}) { // our token can be transferred no more than once (from admin to attendee) if token.numTransfers > (1 as UInt64) { panic("Ticket is non-transferrable!") } self.metadataObjs[token.id] = metadata
emit Deposit(id: token.id, to: self.owner?.address) // log the transfer (increases numTransfers by 1) token.transfer() self.ownedNFTs[token.id] <-! token }
// rest of these are pretty straightforward pub fun idExists(id: UInt64): Bool { return self.ownedNFTs[id] != nil }
pub fun getIDs(): [UInt64] { return self.ownedNFTs.keys }
pub fun updateMetadata(id: UInt64, metadata: {String: String}) { self.metadataObjs[id] = metadata }
pub fun getMetadata(id: UInt64): {String : String} { return self.metadataObjs[id]! }
destroy() { destroy self.ownedNFTs } }
// will need to create an empty collection for any account that wants our NFT pub fun createEmptyCollection(): @Collection { return <- create Collection() }
// can explicitly share NFTMinter resource with another admin so that they can mint tickets pub resource NFTMinter { pub var idCount: UInt64
init() { self.idCount = 1 }
pub fun mintNFT(): @NFT { var newNFT <- create NFT(initID: self.idCount) self.idCount = self.idCount + 1 as UInt64 return <-newNFT }
}
// launching the contract does 3 things: init() { // 1) save a fresh collection to the admin's storage self.account.save(<-self.createEmptyCollection(), to: /storage/NFTCollection)
// 2) allow public access to NFTReceiver functions through this reference self.account.link<&{NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)
// 3) save NFTMinter resource to private storage self.account.save(<-create NFTMinter(), to: /storage/NFTMinter) }
}
|
关于Cadence,Flow官方文档要比我介绍的更清楚,不过在更高的层面来说,Cadence使用 Resources(资源)和Capabilities(能力)来定义谁(Who)可以访问什么(What)功能。
例如,我们讲NFTCollection和NFTMinter资源存入部署账号的/storage/
路径,这意味着 这些资源是私有的。但是我们在/public/
路径下发布一个指向NFTReceiver能力的链接。 另外需要注意的是,我们的NFT只是简单的利用其整数ID定义,并采用一个numTransfers计数器 来记录NFT的存入次数。
在这个示例中,如果某人试图再次转让我们的票证,交易将失败。将合约存入名为cadence/contracts/
的目录。
在我们部署合约之前,我们需要创建flow.json文档来指定谁(Who)在哪里(Where)部署什么(What)。 在项目目录中执行以下命令初始化这个文件:
这会给我们一个启动账号以及相应的私钥。稍后我们将查看flow.json文件,但是首先我们需要为 参与者创建一个账号。运行下面的代码来生成密钥对:
保存上述命令生成的密钥对,然后运行:
1
|
flow accounts create ATTENDEE_PUB_KEY
|
将ATTENDEE_PUB_KEY替换为你刚刚生成的公钥。
记录下来0x开头的地址。现在我们具备了flow.json需要的所有资料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
{ "emulators": { "default": { "port": 3569, "serviceAccount": "emulator-account" } }, "contracts": { "NonFungibleTicket": "./cadence/contracts/NonFungibleTicket.cdc" }, "networks": { "emulator": "127.0.0.1:3569", "mainnet": "access.mainnet.nodes.onflow.org:9000", "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { "emulator-account": { "address": "f8d6e0586b0a20c7", "keys": "e61fd9cbcf7d7d0918c5d02f79c9be08717ca82b5e7bd8c151e009eeb384bb78" }, "attendee-account": { "address": "01cf0e2f2f715450", "keys": "ATTENDEE_PRIVATE_KEY" } }, "deployments": { "emulator": { "emulator-account": ["NonFungibleTicket"] } } }
|
注意:永远不要共享你的私钥。
你将看到我们在NonFungibleTicket合约中添加了一个指针(Pointer),我们的新的参与者账号 以及仿真器账号(标识我们的票证发行人)的合约部署。现在我们可以用下面的命令部署合约:
如果一切顺利,你将会看到下面这样的输出:
1 2 3
|
Deploying 1 contracts for accounts: emulator-account NonFungibleTicket -> 0xf8d6e0586b0a20c7 ✨ All contracts deployed successfully
|
4、铸造Flow区块链的门票NFT
现在是时候创建我们的第一个NFT了。下面我们将使用Cadence,但是我们将使用交易而不是定义 一个合约。交易是我们使用智能合约中定义的函数的方法,交易执行将导致区块链状态的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
import NonFungibleTicket from 0xf8d6e0586b0a20c7
// This transaction allows the Minter account to mint an NFT // and deposit it into its collection.
transaction {
// The reference to the collection that will be receiving the NFT let receiverRef: &{NonFungibleTicket.NFTReceiver}
// The reference to the Minter resource stored in account storage let minterRef: &NonFungibleTicket.NFTMinter
prepare(acct: AuthAccount) { // Get the owner's collection capability and borrow a reference self.receiverRef = acct.getCapability<&{NonFungibleTicket.NFTReceiver}>(/public/NFTReceiver) .borrow() ?? panic("Could not borrow receiver reference")
// Borrow a capability for the NFTMinter in storage self.minterRef = acct.borrow<&NonFungibleTicket.NFTMinter>(from: /storage/NFTMinter) ?? panic("Could not borrow minter reference") }
execute { // Use the minter reference to mint an NFT, which deposits // the NFT into the collection that is sent as a parameter. let newNFT <- self.minterRef.mintNFT()
let metadata : {String : String} = { "event": "FLOW LIVE in Concert!", "section": "200", "row": "3", "seat": "1", "uri": "https://flow-ticket-exchange.s3.amazonaws.com/ticket.png" }
self.receiverRef.deposit(token: <-newNFT, metadata: metadata)
log("Ticket minted and deposited into admin account") } }
|
对我而言,这部分有趣的环节是NFT的元数据。我创建了一个演示用的具有若干属性的票证, 例如区域和排,以及一个指向票证图像的URI链接。
这引起了我的思考,我不知道是否轻量级NFT用户理解其工作原理。
区块链在跟踪NFT的持有人以及其相关的元数据方面表现出色。然而,数字资产更常见的实现 方式是采用外部存储来保存这些资产的实际内容。
作为S3 bucket服务的用户,没有什么可以阻止我删除或更新这些文件!
想象一下,你花费3万美元购买了Steph Curry的3分球,然而Dapper Lab悄悄地将其替换为 Alex Caruso的罚球!希望像IIPFS这样的去中心化存储方案能够解决这一类问题。
我们的票务发行账号部署合约,因此该账号在其私有存储中保存了NFTMinter资源。必须使用 这个账号来签名如下交易:
1
|
flow transactions ./cadence/transactions/MintTicket.cdc send --signer emulator-account
|
如果我们尝试用参与者账号签名,交易就会失败。接下来让我们用一个Cadence脚本来检查我们 的票务签发账号的余额。
1 2 3 4 5 6 7 8 9 10 11
|
import NonFungibleTicket from 0xf8d6e0586b0a20c7
pub fun main() : [UInt64] { let acct1 = getAccount(0xf8d6e0586b0a20c7) let capability1 = acct1.getCapability<&{NonFungibleTicket.NFTReceiver}>(/public/NFTReceiver)
let receiverRef1 = capability1.borrow() ?? panic("Could not borrow the receiver reference")
return receiverRef1.getIDs() }
|
使用如下命令运行脚本:
1
|
flow scripts execute ./cadence/scripts/CheckTicketBalance.cdc
|
然后你将看到一个包含所持有NFT的ID的数组:
这表示发行账号目前持有我们新铸造的NFT!
5、使用Cadence脚本转让门票NFT
现在我们将把门票转让给一位热切等待的乐迷。首先我们将在参与者的存储中 创建一个NFTCollection资源。
这让我们有机会了解FLow架构的一个有用的方面。
在以太坊中,如果你将以太币发送到一个无效的钱包地址,这些以太币就没了。 然而在FLow中,在没有明确的目标地址时资源不可能发送出去,或者整个交易回滚。 我们不会因为手误发送到无效地址而失去门票。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
import NonFungibleTicket from 0xf8d6e0586b0a20c7
// This transaction configures a user's account // to use the NFT contract by creating a new empty collection, // storing it in their account storage, and publishing a capability transaction { prepare(acct: AuthAccount) {
// Create a new empty collection let collection <- NonFungibleTicket.createEmptyCollection()
// store the empty NFT Collection in account storage acct.save<@NonFungibleTicket.Collection>(<-collection, to: /storage/NFTCollection)
log("Collection created for account 2")
// create a public capability for the Collection acct.link<&{NonFungibleTicket.NFTReceiver}>(/public/NFTReceiver, target: /storage/NFTCollection)
log("Capability created") } }
|
使用以上交易运行如下命令:
1
|
flow transactions send .\cadence\transactions\SetupEmptyCollection.cdc --signer attendee-account
|
现在我们的参与者已经准备接收票据了。我们将使用Cadence交易来完成这个操作,在这个交易中 发行账号取出其NFT然后存入参与者的藏品存储。
别忘了每次存入时,合约都会增加存储在NFT中的numTransfers参数的值。在这个交易之后,numTransfers = 1. 下面是合约的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
import NonFungibleTicket from 0xf8d6e0586b0a20c7
// This transaction transfers an NFT from one user's collection // to another user's collection. transaction {
// The field that will hold the NFT as it is being // transferred to the other account let transferToken: @NonFungibleTicket.NFT let metadata: { String : String }
prepare(acct: AuthAccount) {
// Borrow a reference from the stored collection let collectionRef = acct.borrow<&NonFungibleTicket.Collection>(from: /storage/NFTCollection) ?? panic("Could not borrow a reference to the owner's collection") self.metadata = collectionRef.getMetadata(id: 1) // Call the withdraw function on the sender's Collection // to move the NFT out of the collection self.transferToken <- collectionRef.withdraw(withdrawID: 1) }
execute { // Get the recipient's public account object let recipient = getAccount(0x01cf0e2f2f715450)
// Get the Collection reference for the receiver // getting the public capability and borrowing a reference from it let receiverRef = recipient.getCapability<&{NonFungibleTicket.NFTReceiver}>(/public/NFTReceiver) .borrow() ?? panic("Could not borrow receiver reference") // Deposit the NFT in the receivers collection receiverRef.deposit(token: <-self.transferToken, metadata: self.metadata)
log("NFT ID 1 transferred from account 2 to account 1") } }
|
使用下面的命令转让门票:
1
|
flow transactions send ./cadence/transactions/TransferTicket.cdc --signer emulator-account
|
你可以分别用两个账号运行CheckTicketBalance脚本,验证下getIDs()在使用发行账号时 返回空数组,而在使用参与者账号时返回[1]
!接下来让我们看看如果试图将门票转回 发行账号会发生什么情况。
1 2 3 4 5 6 7
|
❌ Transaction Error execution error code 100: Execution failed: error: panic: Ticket is non-transferrable! --> f8d6e0586b0a20c7.NonFungibleTicket:59:16 | 59 | panic("Ticket is non-transferrable!") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
我们的智能合约正确地杜绝了这种情况的发生。我们的门票在二级市场不可以再次销售!
6、门票NFT应用的React前端实现
我们不会深入介绍前端应用的细节,它主要是利用FLow JS库访问我们的Cadence合约。 在这个简单的示例程序中,我们读取NFT的元数据,但是你可以用同样的方法执行任何Cadence代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
import React, { useState, useEffect } from "react"; import * as fcl from "@onflow/fcl"; import { Address } from "@onflow/types"; import * as t from "@onflow/types"
const TokenData = () => { const [user, setUser] = useState({loggedIn: null}) useEffect(() => fcl.currentUser().subscribe(setUser), []) const [nftInfo, setNftInfo] = useState(null) const fetchTokenData = async () => { const encoded = await fcl .send([ fcl.script` import NonFungibleTicket from 0xf8d6e0586b0a20c7 pub fun main() : {String : String} { let nftOwner = getAccount(0xf8d6e0586b0a20c7) let capability = nftOwner.getCapability<&{NonFungibleTicket.NFTReceiver}>(/public/NFTReceiver) let receiverRef = capability.borrow() ?? panic("Could not borrow the receiver reference") return receiverRef.getMetadata(id: 1) } ` ]) const decoded = await fcl.decode(encoded) setNftInfo(decoded) }; return ( <div className="listing"> <div className="center"> <button className="btn-primary" onClick={fetchTokenData}>Fetch Token Data</button> </div> { nftInfo && <div> <div className="center"> <p>Event: {nftInfo["event"]}</p> <p>Section: {nftInfo["section"]}</p> <p>Row: {nftInfo["row"]}</p> <p>Seat: {nftInfo["seat"]}</p> </div> <div className="center image"> <img src={nftInfo["uri"]} alt="My NFT!" width="90%"/> <div> <button onClick={() => setNftInfo(null)} className="btn-secondary">Clear Token Info</button> </div> </div> </div> } </div> ); };
const OwnerData = (account) => { const [ownerInfo, setOwnerInfo] = useState(null) const fetchOwnerData = async () => { const encoded = await fcl .send([ fcl.args( fcl.arg(account, t.Address) ), fcl.script` import NonFungibleTicket from 0xf8d6e0586b0a20c7 pub fun main() : [UInt64] { let acct1 = getAccount(account) let capability1 = acct1.getCapability<&{NonFungibleTicket.NFTReceiver}>(/public/NFTReceiver) let receiverRef1 = capability1.borrow() ?? panic("Could not borrow the receiver reference") return receiverRef1.getIDs() } ` ]) const decoded = await fcl.decode(encoded) setOwnerInfo(decoded) }; return ( <div> <p>Account: {account}</p> <p>Owned NFT's: {ownerInfo}</p> </div> ); }; export default TokenData;
|
使用如下命令启动我们的前端应用:
下面就是我们的简单但强大的去中心化门票查看网页:
完整的代码可以从这里下载。
https://gitee.com/cexchange/CoinExchange?_from=gitee_search#http://118.25.133.182:84/#/
特色
特色1: 基于内存撮合引擎,与传统基于数据库撮合更快
特色2: 前后端分离,基于Token的Api授权机制
特色3: 基于SpringCloud微服务架构,扩展更容易
特色4: MySQL、MongoDB、Redis多种数据存储方式,只为更快
特色5: Kafka发布订阅消息队列,让订单更快流转
特色6: 主流币种对接区块链接口齐全,开箱即用
特色7: 冷热钱包分离,两种提现方式,保证安全
特色8: 机器人系统,同步行情,维护深度,防止搬砖
特色9: 原生App,Java和ObjectC提供原生体验
特色10: 交易所设计者提供技术支持,部署+二开无忧
特色11: 支持添加自定义平台币及其他币种
声明一:我已在新公司上班,一些说明性的东西我会抽空在这里更新,以方便大家编译、搭建、开发
声明二:APP源码及交易机器人源码未开源,有需要的添加QQ:877070886
声明四:请不要用本开源代码直接搭建交易所!本源码尚有一些隐藏BUG,仅供学习!否则后果自负!
声明五:本交易所完整源码仅向有技术团队或技术实力的人提供,小白或不同技术的请勿咨询!
新功能持续开发中,敬请期待
- 代理商系统(100%,5月已完成,未开源)
- 极速兑换(100%,6月已完成,未开源)
- IEO首发活动(100%,6月已完成,未开源)
- 永续合约(100%,8月完成,不开源)
- 期权合约(100%,9月完成,不开源)
- 秒合约(0%,11月完成,不开源)
- 指数合约(0%,12月完成,不开源)
- 差价合约(0%,1月完成,不开源)
- 交割合约(0%,2月完成,不开源)
- 智能客服系统(0%,1月完成,不开源)
系统架构概要
随便画的几个草图,凑合看吧。。。
整体
逻辑架构
部署架构
依赖关系
关于SpringCloud
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。 一般而言,一个完整的SpringCloud框架应该如下图所示:
关于撮合交易引擎
本系统对交易队列采用内存撮合的方式进行,以Kafka做撮合订单信息传输,MongoDB持久化订单成交明细,MySQL记录订单总体成交。 其中01_Framework/Exchange项目主要负责内存撮合,01_Framework/Market项目主要负责订单成交持久化、行情生成、行情推送等服务,包括:
- K线数据,间隔分别为:1分钟、5分钟、15分钟、30分钟、1小时、1天、1周、1月
- 所有交易对的市场深度(market depth)数据
- 所有交易对的最新价格
- 最近成交的交易对
内存撮合交易支持的模式
- 限价订单与限价订单撮合
- 市价订单与限价订单撮合
- 限价订单与市价订单撮合
- 市价订单与市价订单撮合
限价&市价订单处理逻辑
撮合引擎支持的其他功能
除了普通的限价与市价撮合交易功能外,本系统的撮合交易引擎还引入了活动成交模式,通过设置交易对(如:BTC/USDT)的开始交易时间、初始发行量、初始发行价、活动模式等参数,可以制定出丰富的撮合交易模式,从而满足不同的撮合模式。
举例说明
交易所预计在2020年8月8日 12时00分00秒上线交易对AAA/USDT,但是作为一个新上线的币种,没有活动怎么能行呢?项目方或交易所决定拿出10000个AAA以0.0001USDT(市场行情价:0.0005)的价格让大家抢购。本系统支持对这种活动的设置。
另外,如果项目方或交易所决定拿出10000个AAA以0.0001USDT的价格发行,不希望大家抢购,而是希望所有充值USDT的用户能够平均瓜分10000个AAA,本系统也支持这种活动的设置。
总结
总之,本系统支持高度自定义的撮合模式,同时你也可以开发出你自己想要的撮合交易模式,只需要通过修改Exchange项目中的撮合逻辑就可以。
关于技术构成
- 后端:Spring、SpringMVC、SpringData、SpringCloud、SpringBoot
- 数据库:Mysql、Mongodb
- 其他:redis、kafka、阿里云OSS、腾讯防水校验
- 前端:Vue、iView、less
这个是给客户做的,但是后来客户不运营了,所以遗留了这个网站,因为我没有服务器权限,所以这个网站随时可能无法访问。
搭建起来一个测试站点需要购买好几台云服务器,成本较大,所以我自己没有搭建测试站,但是系统是完整的,经过了将近一年的商用及实际运营考验。
关于交易机器人
交易机器人是自动交易的程序,可以根据外部行情,自动进行交易,让本交易所的交易对价格与外部保持一致,防止因部分用户“搬砖”导致的损失。
关于搬砖
例如A交易所BTC价格是10000USDT,而B交易所的BTC价格是9500USDT,搬砖就是在B交易所通过9500USDT的价格买入BTC,然后转账到A交易所,赚取差价(500USDT)。
如果交易所没有交易机器人,就会导致本交易所的币种价格与其他主流交易所相比有差价,从而让用户“搬砖”,导致交易所损失。
另外,交易机器人还有一个功能,就是在交易所初期运营的时候,形成一个初期的交易深度,不会让用户觉得交易所冷清,没有用户。
我本人是Java程序员,对移动端开发不太了解,所以包调试安装实际上也是我付费请别人帮忙的。
如果你没有技术人员,我可以帮助你搭建一套完整的交易所系统,但是需要你请一到两名维护人员,因为系统的稳定运行少不了运维人员。
联系QQ:877070886
==============================================
系统运行环境
- Centos 6.8
- MySQL 5.5.16
- Redis-x64-3.2.100
- Mongodb 3.6.13
- kafka_2.11-2.2.1
- nginx-1.16.0
- JRE 8u241
- JDK 1.8
- Vue
- Zookeeper
生产环境建议配置
文件目录说明
00_framework
└─———admin 后台管理API
└─———bitrade-job 任务管理
└─———chat OTC聊天
└─———cloud SpringCloud微服务管理
└─———core 核心
└─———exchange 撮合交易引擎
└─———exchange-api 撮合交易API
└─———exchange-core 撮合交易核心
└─———jar 第三方类库
└─———market 市场行情API、K线生成
└─———otc-api OTC交易API(如无需otc功能可不启动)
└─———otc-core OTC核心
└─———sql SQL脚本
└─———ucenter-api 用户个人中心API
└─———wallet 钱包资产管理,负责与RPC对接
01_wallet_rpc
└─———bitcoin
└─———bsv
└─———btm
└─———eos
└─———erc-eusdt
└─———erc-token(可对接各种ERC20币种)
└─———eth
└─———ltc
└─———usdt
02_App_Android
03_App_IOS
04_Web_Admin
05_Web_Front
使用教程
- 准备mysql数据库,创建名称为“xxxx”的数据库
- 准备redis缓存数据库
- 准备kafka流式处理环境(先配置运行zookper,接着配置运行kafka)
- 准备mongodb数据库环境,创建用户admin、xxxx,创建bitrade数据库
- 准备阿里云OSS(修改项目中需要配置的地方)
- 准备nginx,修改配置文件(可选,正式上线需配置)
- 修改framework代码中的配置文件为准备环境配置参数
- 编译生成jar可执行文件
- 运行cloud.jar(微服务注册中心)
- 运行exchange.jar(撮合交易引擎)
- 运行market.jar(行情中心,需要等待Exchange.jar完全启动)
- 运行ucenter.jar(用户中心)
- 运行其他模块(wallet.jar、chat.jar、otc-api.jar等)
- 打开mysql,导入framework代码中的sql文件夹中xxxxxxx.sql文件,注意,trigger的sql如果报错,需要针对wallet表添加trigger
- 运行前端vue项目
- 运行后端vue项目
- 运行钱包RPC
- 运行自动交易机器人程序(本部分代码未上传,但不影响)
- 运行Admin项目(该服务并不依赖其他服务,因此也可只运行此项目,直接查看后台)
技术支持
本数字货币交易系统系我所在公司为交易所开发的项目,该交易所因团队原因已停止运营,我司也已于2月解散。因我参与项目时,负责整体研发管理、架构设计以及客户对接,所以掌握所有代码。
本系统在功能使用上有一些需要特别注意的地方,例如新建交易对以后的其他操作,操作不当会引起数据紊乱的错误出现。
本人可提供有偿技术帮助与使用培训指导!