USDT是由Tether公司发行的基于比特币区块链的一种去中心化数字货币,做为当前数字货币市场的主流锚订货币之一,其官方承诺将严格遵照与美圆1:1的比例准备保证金。在技术层面,USDT是基于Omni协议发行的代币,在Omni共识网络上令牌id为31。Omni是一个能够自由发行数字货币的平台,它彻底基于比特币协议,并在原有的比特币核心上增长了新的共识网络,相似与HTTP协议基于TCP协议。html
OmniCore是Omni协议的C++实现,彻底采用与bitcoin的区块数据,因此若是须要同时集成USDT与BTC,实际上只须要使用OmniCore一个核心钱包便可。java
本文的主要内容是介绍如何在服务端集成OmniCore实现USDT钱包的基本功能c++
示例代码仓库 github.com/initsysctrl…git
参数说明:github
Step1:安装git和pkg-config,已经安装过的能够跳过shell
sudo apt-get install git
sudo apt-get install pkg-config
复制代码
Step2:Clone OmniCorejson
git clone https://github.com/OmniLayer/omnicore.git
复制代码
Step3:安装依赖项api
首先安装必须的构建工具数组
sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils
复制代码
而后安装boost,为了兼容各个系统版本,建议安装全部的boost开发包浏览器
sudo apt-get install libboost-all-dev
复制代码
最后安装BerkeleyDB。尽管Ubuntu自带libdb-dev,但一样为了钱包的兼容性,建议使用下面的版本
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:bitcoin/bitcoin
sudo apt-get update
sudo apt-get install libdb4.8-dev libdb4.8++-dev
复制代码
本文中并不须要构建Bitcoin-Qt,因此没有依赖ZMQ和GUI。
Step4:开始构建OmniCore
上一步依赖安装完毕后,进入OmnIcore安装目录
cd omnicore/
复制代码
执行构建脚本
./autogen.sh
./configure
make
复制代码
启动以前,须要配置位于工程目录之下的.bitcoin文件夹中的OmniCore配置文件bitcoin.conf
server=1
txindex=1
rpcuser=你的rpc用户名
rpcpassword=你的rpc密码
rpcallowip=127.0.0.1
rpcport=8332
paytxfee=0.00001
minrelaytxfee=0.00001
datacarriersize=80
logtimestamps=1
omnidebug=tally
omnidebug=packets
omnidebug=pending
复制代码
server=1
表明开启RPC访问
txindex=1
表明事务初始索引
recuser
和rpcpassword
表明rpc访问的身份验证,
rpcallowip
和rpcport
表明容许访问钱包的ip地址及端口。
paytxfee
和minrelattxfee
控制bitcoin交易的手续费,Omni交易也属于一种特殊的比特币交易,打包与广播也须要向矿工支付费用。手续费设置太低会形成交易确认慢甚至交易失败,手续费太高会形成资源的浪费(以2018.09.13的BTC价格换算,每多消耗0.0001btc须要浪费4rmb),因此设置动态配置交易手续费十分必要。预估比特币交易手续费可使用下面的网址bitcoinfees.earn,buybitcoinworldwide。假设当前预估的比特币交易费率为0.0000001BTC/Byte,那么须要设置paytxfee=0.00001BTC/kByte
。
上述构建完成后,进入omnicore/src
目录,开始启动钱包,启动时能够配置启动项以选择不一样的网络。
./omnicored -testnet
链接test3测试网络,会同步test3网络的区块数据(约20G)./omnicored -regtest
单机运行,不须要链接其余网络,区块数据在本地运行。./omnicored
链接比特币主网网络,会同步真实区块数据(约180G)。钱包初始化完成后,将自动开始同步区块。启动主网或测试网后须要同步一段较长的时间,在这段时间内不要进行任何交易。能够新开一个终端链接钱包所在服务器,经过getinfo
和omni_getinfo
能够查看底层bitcoin信息和上层omni信息。区块浏览器(Test3区块浏览器)能够做为区块同步的参考。同步将做为守护进程在后台执行,若是须要中止,使用指令 ./omnicore-cli stop
。前期开发建议在test3测试网络上进行。
$ ./omnicore-cli getinfo
{
"version": 130200,
"protocolversion": 70015,
"walletversion": 130000,
"balance": 4.85909131, //钱包比特币总余额
"blocks": 1413349,
"timeoffset": 0,
"connections": 0,
"proxy": "",
"difficulty": 56234572.68927951,
"testnet": true, //是不是测试网
"keypoololdest": 1535371434,
"keypoolsize": 100,
"paytxfee": 0.00010000,
"relayfee": 0.00001000,
"errors": ""
}
复制代码
$ ./omnicore-cli omni_getinfo
{
"omnicoreversion_int": 30001000,
"omnicoreversion": "0.3.1", //omni core 版本
"mastercoreversion": "0.3.1",
"bitcoincoreversion": "0.13.2", //基于比特币版本
"block": 1413349,//区块要举高高
"blocktime": 1536841368,
"blocktransactions": 0,
"totaltrades": 15601,
"totaltransactions": 43731,
"alerts": [
]
}
复制代码
json-rpc是一种轻量级传输协议,定义一个完整网络请求中请求对象的格式和响应对象的格式。与rest api相比,仅仅只是数据格式的差别而已,网络请求的自己并无什么差异。
请求对象:
{
"jsonrpc": "2.0",//rpc版本号
"method": "your_method",//方法名
"params": [//参数数组
"var1",
"var2"
],
"id": 9527//请求
}
复制代码
响应对象(正确):
{"jsonrpc": "2.0", "result": "this is result", "id": 9527}
复制代码
响应对象(错误):
{
"result": null,
"error": {
"code": -32601,//错误码
"message": "Method not found"//错误缘由
},
"id": 9527
}
复制代码
请求的协议是http,请求的地址是钱包主机地址。身份验证信息将以Authorzation的形式添加到headers中,方法、参数、id信息将以raw的形式添加到hbody中:
返回的结果以下:
服务端,以java为例:
String RPC_USER = "your_user_name";
String RPC_PASSWORD = "your_password";
RestTemplate client = new RestTemplateBuilder()
.basicAuthorization(RPC_USER, RPC_PASSWORD)
.rootUri(URL)
.build();
client.postForObject(URL, baseRpcReq, BaseRPCresponse.class);
复制代码
或者采用jsonrpc4j
,这种方式能够捕捉异常便于调试:
<!--JSON-PRP handler-->
<dependency>
<groupId>com.github.briandilley.jsonrpc4j</groupId>
<artifactId>jsonrpc4j</artifactId>
<version>1.5.3</version>
</dependency>
复制代码
String RPC_USER = "your_user_name";
String RPC_PASSWORD = "your_password";
String cred = Base64.encodeBase64String((RPC_USER + ":" + RPC_PASSWORD).getBytes());
Map<String, String> headers = new HashMap<>(1);
headers.put("Authorization", "Basic " + cred);
try {
this.mClient = new JsonRpcHttpClient(new URL(URL), headers);
} catch (MalformedURLException e) {
e.printStackTrace();
}
复制代码
rpc仅仅只是一种数据请求的固定格式,username和passwrod并不能保证访问的安全性。钱包须要配置rpcallowip字段来限定运行访问钱包的ip地址,默认状况下为localhost
,在测试节点,可使用0.0.0.0/0
开启无限制访问。
Omnicore的指令集彻底兼容bitcoin,除了与omni令牌相关的指令集外,其他的指令集都来所有继承自bitcoin-cli。下面为钱包建立的核心指令集,更详细的内容能够从OmniCore JSON-RPC和BitCoin JSON-RPC进行查询。
由于Omnicore底层基于Bitcoin,因此USDT地址实际上就是BTC地址,当前的比特币钱包采用的是deterministic wallet钱包模式,使用如树状层级推导 (hierarchical deterministic) 的推导方式,从一个随机数生成源推导全部地址密钥。因此一个USDT钱包中,全部的地址实际上来自同一个种子源。若是是测试网络,地址通常以"m","n"获取新地址能够指定account名称,若是不指定,那么会分配到默认帐户。
$ ./omnicore-cli getnewaddress
mkRj6TFspkyso96LvDTJq77DwoqoFMBEcJ
复制代码
$ ./omnicore-cli getnewaddress feeaccount
n1WuiWX5zjmz7MVymUtsdSnC325xQ1v4SR
复制代码
一个帐户名能够对应多个地址
$ ./omnicore-cli getaddressesbyaccount feeaccount
[
"mk8cMZBX7v7zzzc9FHBMQbRNVPwRtq9CZ2",
"n1WuiWX5zjmz7MVymUtsdSnC325xQ1v4SR"
]
复制代码
若是是正式环境,那么必须使用其余地址转帐或提现到新地址才能获取BTC和USDT。若是使用regtest本地网络,那么须要经过挖矿得到比特币。若是使用test3测试网络,那么TBTC能够从coinfaucet或者faucet获取。但在测试网络是没有测试usdt的,全部只能用test omni代替usdt进行测试。发送TBTCmoneyqMan7uh8FqdCA2BV5yZ8qVrc9ikLP
便可得到少许TOMNI,汇率为100 TOMNI/1 TBTC,令牌id分别为1和2。
比特币实际上没有“余额”这个概念,只有UTXO(Unspent Transaction Outputs)。在传统的交易系统中,从A地址转给B地址100个单位的资产的过程是把A地址下的余额减100,B地址下的余额加100,两步必须知足原子性。但在比特币中A地址下并无余额,只有一张张零碎的“支票”,记录着每一笔转入资金,转帐的过程其实是把一张或者多张“支票”凑起来花费掉,没有花掉的部分做为“找零”返回给找零地址。因此通常须要把找零地址设置为发送地址,若是没有的话,系统将在钱包中随机挑选一个地址做为“找零地址”。
列出比特币UTXO:
方法: listunspent
参数:
返回:UTXO列表
$ ./omnicore-cli listunspent 0 999999 '["mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC"]'
[
{
"txid": "ef6e77063dff8988b82044286b3d3f022df5a14aae260179a95c2e80c0e47ec4",//来源
"vout": 2,
"address": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//地址
"account": "account0",//所属帐户
"scriptPubKey": "76a914293d87f697ca96ffb00f049b60645e5c8979498488ac",
"amount": 0.00054600,//支票金额
"confirmations": 799,
"spendable": true,//可花费?
"solvable": true
}
]
复制代码
USDT的转帐其实是代号为31的OmniCore令牌转帐。Omnicore提供了多套api实现令牌转帐功能,v0.3.1版本以前,可使用omni_send
和omni_sendall
。这种方式必须保证发送地址上不只须要有令牌余额,还须要有必定数量的比特币用于支付手续费。从v0.3.1版本开始,Omnicore提供了两个新的api omni_funded_send
和omni_funded_sendall
,这种方式的好处在于能够指定手续费的支付方,全部的令牌交易均可以使用统一的地址进行支付比特币手续费,而不须要发送者自身拥有比特币。但这里并未设定手续费的具体数量,系统将根据在配置文件中的关于手续费的配置文件进行动态设定。
方法 omni_funded_send
参数
fromaddress (string,必选) 令牌发送者
toaddress (string,必选) 令牌接收者
propertyid (number,必选) 令牌id
amount (string,必选) 发送金额
feeaddress (string,可选)用于支付手续费的地址,若是设置此地址,那么此地址上必须拥有比特币
返回:事务 hex
$ ./omnicore-cli omni_funded_send mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf 1 5 mpaumxor659PhoJhXp1VCVHVwbFCZSRmuf
a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad
复制代码
错误返回:
{
error code: -212
error message:Error choosing inputs for the send transaction
}
复制代码
发送USDT或其余令牌的过程属于一种比较特殊的比特币交易,交易的打包广播一样须要支付矿工费用,费用过低交易将没法成功。发送令牌的过程可能会出现各类错误,能够检查发送者地址是不是本地钱包地址令牌余额是否充足、feeaddress是不是本机钱包地址、比特币余额是否充足。
除了使用基本的api外,还可使用 Raw Transaction API 建立并广播事务,但过程至关的繁琐,须要通过七步构建。通常状况下不建议这么作,但若是须要将打包签名的过程与发送的过程进行分离,那么就必须使用这种方式。例如某些状况下,须要在冷钱包中签名,而后在热钱包中广播。
方法:omni_gettransaction
参数:hex(string_64位事务哈希),发送交易后的交易哈希txid
返回 :
$ ./omnicore-cli omni_gettransaction a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad
{
"txid": "a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad",//交易哈希
"fee": "0.00002765",//手续费金额
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//发送者
"referenceaddress": "mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf",//接受者
"ismine": true,//是否本机地址
"version": 0,
"type_int": 0,
"type": "Simple Send",
"propertyid": 1,
"divisible": true,
"amount": "5.00000000",//发送令牌金额
"confirmations": 0//确认数,,默认状况下,>5通常才认为交易有效
}
复制代码
$ ./omnicore-cli omni_gettransaction a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad
{
"txid": "a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad",//交易哈希
"fee": "0.00002765",
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//发送address
"referenceaddress": "mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf",//接收address
"ismine": true,
"version": 0,
"type_int": 0,
"type": "Simple Send",//交易类型
"propertyid": 1,
"divisible": true,
"amount": "5.00000000",
"valid": true,//已经成功
"blockhash": "00000000000000460219a9fe9761cb92120eb7d67b640d2b643a0a05185fa2a0",
"blocktime": 1536324400,
"positioninblock": 1391,
"block": 1412595,
"confirmations": 1123//经过节点确认
}
复制代码
方法:omni_listtransactions
参数:
addfilt | string | 可选 | 地址过滤 (default: "*" ) |
---|---|---|---|
count |
number | 可选 | 最大数量(default: 10 ) |
skip |
number | 可选 | 跳过第n个事务 (default: 0 ) |
startblock |
number | 可选 | 起始的区块(default: 0 ) |
endblock |
number | 可选 | last block to include in the search (default: 999999 ) |
请求:
$ ./omnicore-cli omni_listtransactions
[
{
"txid": "a25260a79243a48df21ca2d9fba2209818ea1339026d91b6476d531929c52dad",//事务哈希
"fee": "0.00002765",//手续费
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//发送
"referenceaddress": "mqrA5Ai8XdKe1ob1L2HwyYr3TXUf9nUeBf",//接收
"ismine": true,
"version": 0,
"type_int": 0,
"type": "Simple Send",//类型
"propertyid": 1,//令牌id
"divisible": true,
"amount": "5.00000000",
"valid": true,//是否有效的交易事务
"blockhash": "00000000000000460219a9fe9761cb92120eb7d67b640d2b643a0a05185fa2a0",
"blocktime": 1536324400,
"positioninblock": 1391,
"block": 1412595,
"confirmations": 221//确认数,默认大于5猜有效
},
{
"txid": "2e527c3c85b2a9b21252b50efd6cda31022ee5ebcf9fee451255bea61211b799",
"fee": "0.00002570",
"sendingaddress": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",
"referenceaddress": "mq8fRoxRB9M4vstJ9BrEBaysZVUjxPxoK3",
"ismine": true,
"version": 0,
"type_int": 0,
"type": "Simple Send",
"propertyid": 2,
"divisible": true,
"amount": "3.00000000",
"valid": true,
"blockhash": "0000000000000007c957dc0642c39c26b9bb46327620e18e23244936f894c570",
"blocktime": 1536324082,
"positioninblock": 2115,
"block": 1412594,
"confirmations": 222
}
]
复制代码
若是交易刚刚发送,即没有被验证是否合法,也没有被节点确认,那么该事务将处于pengding 状态使用 omni_listtransactions
不能做为转帐的确认状态。使用omni_listpendingtransactions
能够在缓冲区找到这一类型的事务信息,但pengding状态并不稳定,不能用于确认转帐结果。
查询USDT的余额即查询第31号令牌的余额。
方法:omni_getbalance
参数:
$ ./omnicore-cli omni_getbalance mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC 1
{
"balance": "4.00000000",//余额
"reserved": "0.00000000",
"frozen": "0.00000000"//被冻结,没啥用
}
复制代码
方法:omni_getwalletaddressbalances
将返回钱包内全部余额不为0的地址列表,每一个地址均可能有不一样的令牌余额。令牌id若是为31,那么这个令牌即USDT。
$ ./omnicore-cli omni_getwalletaddressbalances
[
{
"address": "mjH1iB7wt5TC4f8qjvZqtmBXd1aCPSPinC",//地址
"balances": [
{
"propertyid": 1,//令牌id,31=USDT
"name": "Omni",//令牌名称
"balance": "4.00000000",//余额
"reserved": "0.00000000",
"frozen": "0.00000000"
},
{
"propertyid": 2,
"name": "Test Omni",
"balance": "7.00000000",
"reserved": "0.00000000",
"frozen": "0.00000000"
}
]
}
]
复制代码
方法:omni_getwalletbalances
返回:
$ ./omnicore-cli omni_getwalletbalances
[
{
"propertyid": 1,//令牌id
"name": "Omni",//令牌名称
"balance": "11.00000000",//可用余额
"reserved": "0.00000000",
"frozen": "0.00000000"
},
{
"propertyid": 2,
"name": "Test Omni",
"balance": "8.00000000",
"reserved": "0.00000000",
"frozen": "0.00000000"
}
]
复制代码
其余的相关指令集会在源代码中示例出来。
中心化钱包的本质是代替用户托管资产,钱包保存了全部地址的私钥,对上面的令牌有彻底的使用权。对于用户而言,对资产的流动有知情权,但并无实际控制权。一个完整的中心化钱包能够分为两层,记帐层和区块底层,至少须要集成四个基本的业务功能:
USDT地址即比特币区块链上的地址,借助比特币内核 getnewaddress
能够从同一个种子推导出无数个地址,生成地址的过程相似与把一枚硬币连续抛255次。服务端须要在本身的用户系统中为每一个用户生成不一样的地址,用户的看到的资产实际上服务端的记帐状态,并不是真实资产。
钱包一旦启动,会开启同步区块的守护进程,服务端不须要进行手动的区块同步操做。但服务端须要按期的扫描区块以发现并确认充值事务。经过omni_listtransactions
能够查询当前钱包内的事务列表,根据业务须要,能够定时每小时全量扫描一次,每次最多返回100条事务。遍历每条事务,若是事务已经验证且确认数大于等于6,那么被认为是一条有效的充值记录。而后判断记帐层是否已经记录了该事务,若是没有记录则写入充值记录表,同时查询绑定该地址的用户,在余额表中该用户的可用余额加上充值金额。若是已经写入了那么跳过本次事务。单次的事务处理流程以下:
这是最简易的模式,根据业务情景能够适当调整扫描周期和最大事务数。
用户充值后USDT保留在用户绑定的区块地址中,须要及时的转移到中央地址中去。中央地址即保存整个平台资产的一个或者多个地址。可使用与普通用户相同的“种子”,也能够单独使用一个钱包,或者直接使用冷钱包离线保存。在保证安全和效率的状况下,越少的转帐次数越好,能够最大限度的节省手续费。获取钱包地址USDT余额列表有多种方式 ,从v0.3.1开始可使用omni_getwalletaddressbalances
直接返回全部每一个地址的全部令牌列表。一旦检测id=31的令牌余额不为0,且大于最小额度(通常大于预估的手续费)则使用omni_funded_sendall
转移全部的USDT到指定的中央钱包。
但须要注意的是,在Omnicore上从发送者转帐转移指定id的令牌到接受者,当交易被建立且被发送成功后,交易验证须要必定时间,发送者的令牌余额不会当即变化。因此若是扫描余额的时间周期过短,会形成一个地址上的余额被屡次转移,虽然只会有一次成功但会重复消耗手续费,因此建议2-6hour扫描一次本地钱包余额列表。
提现是指用户把实际资产从平台钱包中转移出去,只要判断是本人操做并且提现金额小于可用额度就被认为是有效的提现请求。根据提现地址的不一样有两种状况:
当提现地址是钱包内的地址时(即平台内的另一个用户)属于内部转帐。这种方式并不需在从中央钱包发送USDT到指定地址,只须要在记帐层进行依次对两个帐户上的USDT余额进行修改,几乎没有时间延迟。
当提现地址是否是钱包内的地址时(非平台用户)属于外部转帐。这种方式须要操做区块链,不会立刻进行确认,根据手续费设定和当前比特币主网拥堵情况可能须要几小时到一天的确认时间。
对于外部转帐,若是用户绑定的区块地址上还存在余额,那么优先使用该地址进行转帐,其次选择中央钱包进行转帐。可使用omni_funded_send
来进行建立USDT交易并广播,交易发送成功后会生成的事务哈希。根据事务哈希,经过omni_gettransaction
能够进行提现进度的跟踪。
参考: