Omni Layer USDT區塊鏈開發包簡介【OmniTool.Java】
- 2019 年 12 月 1 日
- 筆記
OmniTool.Java開發包適用於為Java應用快速增加對Omni/USDT數字資產的支援能力,即支援使用自有Omni節點的應用場景,也支援基於第三方API服務和離線裸交易的輕量級部署場景。官方下載地址:http://sc.hubwiz.com/codebag/omni-java-lib/。
1、開發包概述
OmniTool.Java開發包主要包含以下特性:
- 完善的Bitcoin/Omni Layer RPC API封裝
- 支援利用自有節點或第三方服務獲取指定地址的比特幣utxo集合
- 支援離線生成Omni代幣或比特幣轉賬裸交易
- 支援利用自有節點或第三方服務廣播裸交易
OmniTool.Java支援本地部署的Omnicored節點,也支援第三方服務提供的開放API,要增加新的第三方服務也非常簡單,只需要參考程式碼實現如下介面:
- IUtxoCollector:Utxo採集器
- IBroadcaster:裸交易廣播器
OmniTool.Java軟體包當前版本1.0.0,主要類/介面及關係如下圖所示:

OmniTool.Java軟體包主要程式碼文件清單請訪問官網:http://sc.hubwiz.com/codebag/omni-java-lib/
2、RpcClient類使用說明
RpcClient類封裝了比特幣以及Omni Layer的RPC API介面協議。創建RpcClient對象時,需要傳入包含有效身份資訊的節點RPC URL。例如,假設安裝在本機的omnicored節點軟體接入主網,其配置如下:
- rpcuser:user
- rpcpassword:123456
- rpcport:8332
那麼可以使用如下的程式碼來實例化RpcClient:
import omnitool.RpcClient; RpcClient client = new RpcClient( "http://user:[email protected]:8332" /*節點RPC API的URL*/ );
使用RpcClient的call()
方法可以調用Bitcoin層和omni層的所有RPC API。例如,使用listunspent調用來獲取本地節點中指定地址的utxo:
//import java.util.Map; Map[] unspents = client.call( Map[].class, /*返回結果類型*/ "listunspent", /*RPC API名稱*/ 6, /*最小確認數*/ 999999, /*最大確認數*/ new String[]{"mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"} /*地址清單*/ ); for(Object unspent: unspents) { System.out.printf("txid: %sn",(String)unspent.get("txid")); }
call()
方法的返回結果對應於RPC API的JSON響應中的result
欄位,其類型取決於我們傳入的第一個參數。
call()
方法的第一個參數聲明方法返回的結果類型的Class對象,方法會將RPC API的JSON響應中的result欄位解碼為該參數指定的類型。通常我們都可以使用Map
或Map[]
來對應JSON響應中的result欄位的內容,例如上例所示。這種處理方式可以適應不斷變化中的RPC API,但從結果中提取數據時,不得不小心處理類型轉換的問題。
call()
方法的第二個參數聲明要調用的RPC API方法名,從第三個參數開始的其他參數則表示所指定的RPC API方法的參數。
2.1 定義自己的結果類
可選地,也可以自己定義一個類來簡化從call()
方法的返回結果中提取數據的難度。例如,對於上面的示例,我們可以定義一個Unspent
類來描述listupsent
響應中的JSON對象(不需要定義所有的欄位,按自己的需求選擇):
class Unspent{ public String txid; public long vout; public String account; public String scriptPubKey; public double amount; public long confirmations; }
那麼我們可以按如下的方式調用RpcClient:
Unspent[] unspents = client.call( Unspent[].class, /*返回結果類型*/ "listunspent", /*RPC API方法名*/ 6, /*最小確認數*/ 999999, /*最大確認數*/ new String[]{"mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"} /*地址清單*/ ); for(Object unspent: unspents) { System.out.printf("txid: %sn",unspent.txid); System.out.printf("vout: %dn",unspent.vout); System.out.printf("amount:%fn",unspent.amount); }
顯然,定義自己的結果類可以將RPC API的JSON響應直接反序列化到指定的類型,對於操作複雜響應結果會很有幫助。但比特幣和Omni層的RPC API不僅在動態演化中,而且有些JSON響應的結構本身就是動態的,因此往往還需要結合使用前面更通用的Map
或Map[]
類型。
2.2 Omni層RPC API
OmniCore節點在比特幣原有的RPC介面之外,擴充了額外的介面用來操作Omni層的數據,這些擴展的RPC介面採用omni_
前綴以區隔於Bitcoin的原有RPC介面。
例如,獲取某個地址的USDT代幣餘額需要使用Omni層的omni_getbalance調用,下面的程式碼獲取地址1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P
的USDT(資產ID:31)餘額:
Map[] balances = client.call( Map[].class, /*返回結果類型*/ "omni_getbalance" /*Omni RPC API方法名*/ "1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P", /*賬戶地址*/ 31 /*Omni資產ID:USDT=31*/ ); for(Map b:balances){ System.out.printf("balance: %sn",(String)b.balance); }
類似的,可以使用omni_send調用來執行簡單的USDT轉賬。例如,下面的程式碼從地址3M9qvHKtgARhqcMtM5cRT9VaiDJ5PSfQGY
向地址37FaKponF7zqoMLUjEiko25pDiuVH5YLEa
轉入100.0個USDT代幣:
String txid = client.call( String.class, /*返回結果類型*/ "omni_send", /*RPC API方法名*/ "3M9qvHKtgARhqcMtM5cRT9VaiDJ5PSfQGY", /*代幣轉出地址*/ "37FaKponF7zqoMLUjEiko25pDiuVH5YLEa", /*代幣轉入地址*/ 31, /*代幣ID:USDT*/ "100.00" /*轉移的代幣數量*/ ); System.out.printf("tx hash => %sn",txid);
開發包中的demo/RpcClientDemo.java
示例程式碼使用RpcClient完整演示了在Omni層的代幣發行與轉賬功能,如果你計劃搭建自己的Omni Core節點,相信這個示例會有很大幫助。
3、ToolKit類使用說明
如果不願意搭建自己的Omni Core節點,而是希望基於第三方API為自己的Java應用增加對Omni Layer/USDT的支援,那麼最簡單的方法是使用離線交易的入口類ToolKit。
ToolKit類的主要作用是創建並廣播Omni代幣或比特幣轉賬裸交易,它的基本使用步驟如下:
- 創建一個ToolKit實例
- 使用
AddKey()
方法將必要的私鑰加入該ToolKit實例,例如轉出地址的私鑰,因為ToolKit需要利用私鑰對裸交易進行簽名 - 使用
SendOmnicoin()
方法生成並廣播Omni代幣轉賬裸交易,或者使用SendBitcoin()
方法生成並廣播比特幣轉賬裸交易
3.1 Omni/USDT代幣轉賬
使用ToolKit實現的Omni/USDT代幣轉賬示例程式碼如下,說明見注釋:
import omnitool.*; String network = "main"; ToolKit kit = new ToolKit( network, /*接入的網路*/ new KeyStoreMemory(), /*使用記憶體密鑰庫*/ new UtxoCollectorSmartbit(network), /*使用雲端Utxo採集器*/ new UtxoSelectorDefault(), /*使用默認策略Utxo選擇器*/ new BroadcasterSmartbit(network) /*使用雲端裸交易廣播器*/ ); String privHex = "4aec8e45106....00d5c5a05b"; /*私鑰:16進位字元串*/ kit.addKey(privHex); /*將私鑰加入ToolKit*/ String from = kit.getKeyStore() .getByKey(privHex).address; /*私鑰對應的地址作為發起帳號*/ String to = "1GxX5tQR1C.....x2zbdj4mMuDcWR"; /*接收地址*/ String txid = kit.sendOmnicoin( from, /*發送方地址,私鑰必須已經加入錢包*/ to, /*接收方地址*/ 31, /*轉賬代幣ID,USDT=31*/ 10000 /*轉賬代幣數量,調整為最小單位計量的整數*/ null, /*比特幣手續費支付地址,私鑰必須已加入ToolKit*/ 546, /*向接收方發送的流通比特幣,單位:satoshi*/ 1000, /*交易手續費,單位:satoshi*/ true /*是否廣播*/ ); System.out.printf("txid => %sn",txid); /*列印交易哈希*/
注意:
- ToolKit實例利用錢包中的私鑰生成地址列表,並利用這些地址從第三方服務獲取utxo資訊。 因此需要錢包中 的私鑰對應地址在鏈上有utxo存在,ToolKit對象才能夠成功構造並簽名裸交易。
- 轉賬目標地址應當與創建Toolkit對象時指定的網路一致,例如主網的
p2pkh
地址,前綴應當為1
。
3.2 指定Omni交易的手續費支付地址
在Omni協議層不需要支付交易手續費,但是Omni交易所嵌入的比特幣交易依然需要支付手續費。當sendOmnicoin()
方法的手續費支付地址設置為null
時,將使用發送方地址支付比特幣交易手續費。當你的Java應用需要實現多賬戶歸集功能時,使用統一的手續費支付地址會更容易管理一些。
例如,下面的程式碼使用地址35stX1w6LKHj7hGHz6GVNzXZCdUhAeqDb6
支付Omni交易的手續費:
String txid = kit.sendOmnicoin( from, /*發送方地址,私鑰必須已加入ToolKit*/ to, /*接收方地址*/ 31, /*轉賬OMNI代幣ID,31:USDT*/ 10000, /*轉賬OMNI代幣數量,已調整至最小單位*/ "35stX1w6LKH...CdUhAeqDb6" /*交易手續費支付地址,私鑰必須已加入ToolKit*/ 546, /*向接收方發送的流通比特幣,單位:satoshi*/ 1000, /*交易手續費,單位:satoshi*/ true /*是否廣播*/ );
注意:
- 即使指定了餘額充足的手續費支付地址,Omni交易的發送方依然必須有微量的比特幣 餘額(546 SATOSHI),因為Omni協議需要交易發送方至少有一個可用UTXO。
- 手續費支付地址同時也是找零地址,多餘的比特幣將返回至該地址
3.3 指定Omni交易的比特幣轉賬數量
由於Omni交易要求發送方必須有可用的UTXO,因此為了便於接收Omni代幣的地址可以繼續流通所持有的Omni代幣,sendOmnicoin()
方法需要至少向接收方地址轉入546 SATOSHI的比特幣,可以在調用該方法時修改這個默認數值。
例如,下面的程式碼轉入接收方1000個SATOSHI:
String txid = kit.SendOmnicoin( from, /*發送方地址,私鑰必須已加入ToolKit*/ to, /*接收方地址*/ 31, /*轉賬OMNI代幣ID,31:USDT*/ 10000, /*轉賬OMNI代幣數量,已調整至最小單位*/ fundAddr: "35stX1w6LKH...CdUhAeqDb6" /*交易手續費支付地址,私鑰必須已加入ToolKit*/ 1000, /*向接收方發送的流通比特幣,單位:satoshi*/ 1000, /*交易手續費,單位:satoshi*/ true /*是否廣播*/ );
3.4 指定Omni交易的手續費
sendOmnicoin()
方法可以設置交易手續費,例如設置為3000 SATOSHI
:
String txid = kit.SendOmnicoin( from, /*發送方地址,私鑰必須已加入ToolKit*/ to, /*接收方地址*/ 31, /*轉賬OMNI代幣ID,31:USDT*/ 10000, /*轉賬OMNI代幣數量*/ fundAddr: "35stX1w6LKH...CdUhAeqDb6" /*交易手續費支付地址,私鑰必須已加入ToolKit*/ 1000, /*向接收方發送的流通比特幣,單位:satoshi*/ 3000, /*交易手續費,單位:SATOSHI*/ true /*是否廣播*/ );
3.5 僅生成Omni裸交易但不廣播
有時可能只需要生成Omni轉賬裸交易但並不需要廣播出去,可以將sendOmnicoin()
方法的最後一個參數設置為false
來取消廣播,這時將返回生成的裸交易。例如:
String rawtx = kit.SendOmnicoin( from, /*發送方地址,私鑰必須已加入ToolKit*/ to, /*接收方地址*/ 31, /*轉賬OMNI代幣ID,31:USDT*/ 10000, /*轉賬OMNI代幣數量,已調整至最小單位*/ fundAddr: "35stX1w6LKH...CdUhAeqDb6" /*交易手續費支付地址,私鑰必須已加入ToolKit*/ 1000, /*向接收方發送的流通比特幣,單位:satoshi*/ 3000, /*交易手續費,單位:SATOSHI*/ false /*是否廣播*/ ); System.out.println(rawtx); /*列印裸交易內容*/
3.6 比特幣轉賬
OmniTool.Java也支援比特幣轉賬裸交易的生成與廣播。
例如,下面的程式碼從ToolKit的某個地址向其他地址轉10000 SATOSHI
:
String privHex = "4aec8e45106....00d5c5a05b"; /*私鑰:16進位字元串*/ kit.addKey(privHex); /*將私鑰加入ToolKit*/ String from = kit.getKeyStore() .getByKey(privHex).address; /*私鑰對應的地址作為發起帳號*/ String to = "1GxX5tQR1C.....x2zbdj4mMuDcWR"; /*接收地址*/ String txid = kit.sendBitcoin( from, /*發送方地址*/ to, /*接收方地址*/ 10000, /*轉賬比特幣數量,單位:SATOSHI*/ 1500, /*手續費,單位:SATOSHI*/ null, /*找零地址*/ true /*是否廣播*/ );
當找零地址設置為null
時,SendBitcoin()
方法使用發送方地址作為找零地址。下面的程式碼創建一個新地址接收找零:
String changeAddr = kit.newAddress(); /*創建新地址*/ String txid = kit.sendBitcoin( from, /*發送方地址*/ to, /*接收方地址*/ 10000, /*轉賬比特幣數量,單位:SATOSHI*/ 1500, /*手續費,單位:SATOSHI*/ changeAddr, /*找零地址*/ true /*是否廣播*/ );
類似的,當只需要生成裸交易而不希望廣播時,可以設置最後一個參數為false
。
4、UTXO採集器
OmniTool.Java使用介面UtxoCollector
來約定UTXO的採集功能。該介面的實現需要支援獲取指定地址的候選UTXO集合,可指定多個地址。
介面方法:
UtxoBag collect(String[] addresses); /*提取並返回候選UTXO集合*/
參數addresses
用來聲明要收集UTXO的地址清單。
當前實現類:
- UtxoCollectorSmartbit:基於雲端第三方API實現的Utxo採集器
- UtxoCollectorRpc:基於omnicored節點RPC API實現的Utxo採集器
例如,下面的程式碼使用UtxoCollectorSmartbit獲取測試鏈某個指定地址的UTXO:
UtxoCollector collector = new UtxoCollectorSmartbit( "main" /*主鏈*/ ); String[] addresses = new String[]{"1C3TZ...brS2xHM"}; UtxoBag collected = collector.Collect( addresses /*地址清單*/ );
5、UTXO選擇器
OmniTool.Java使用介面UtxoSelector
來約定UTXO的篩選策略。該介面的實現需要根據目標金額從候選UTXO中選擇可用UTXO,並返回新的UtxoBag實例。
介面方法:
UtxoBag select(long target,UtxoBag collected); /*選擇可消費UTXO,返回UtxoBag對象*/
參數target
聲明要達成的最低金額目標,單位:SATOSHI。
參數collected
是候選的utxo集合,通常是UtxoCollector的collect()
調用返回的結果。
當前實現類:
- UtxoSelectorDefault:選擇不少於6個確認的未消費UTXO
例如下面的程式碼使用UtxoSelectorDefault
實例從候選UTXO中刪選出至少100000 SATOSHI
的UTXO:
//collected表示候選UTXO集合,來自Utxo採集器的collect()調用結果 UtxoSelector selector = new UtxoSelectorDefault(); UtxoBag selected = selector.select( 100000, /*最低目標金額*/ collected /*候選UTXO集合*/ ); System.out.printf("total:%dn":selected.getTotal()); /*列印輸出選中utxo總額*/
考慮到UTXO的不可分割性,篩選出的若干UTXO的總和,有可能超過目標金額。可以使用UtxoBag實例的getTotal()
方法查看集合中的UTXO總額,如上。
6、裸交易廣播器
OmniTool.Java使用Broadcaster
介面約定裸交易廣播的功能規格。該介面的實現應當將裸交易廣播到Omni/Btc網路中。
介面方法:
String broadcast(String rawtx); /*廣播裸交易*/
參數rawtx
用來聲明要廣播的裸交易,類型為16進位字元串。
當前實現類:
- BroadcasterSmartbit
- BroadcasterRpc
例如,下面的程式碼使用BroadcasterSmartbit
將裸交易碼流廣播到Omni/Btc網路中:
Broadcaster broadcaster = new BroadcasterSmartbit( "testnet" /*測試鏈*/ ); String txid = broadcaster.broadcast( "01000000011da9283b4...59f58488ac00000000" /*裸交易*/ );
7、密鑰存儲介面
OmniTool.Java使用KeyStore
約定密鑰存儲的功能規格。
介面方法:
bool add(KeyStoreItem item); /*存入密鑰*/ KeyStoreItem[] list(); /*瀏覽全部密鑰*/ KeyStoreItem getByKey(); /*查詢指定16進位私鑰對應的密鑰資訊*/ KeyStoreItem getByWif(); /*查詢指定WIF格式私鑰對應的密鑰資訊*/ KeyStoreItem getByAddress(); /*查詢指定地址對應的密鑰資訊*/ KeyStoreItem getByScript(); /*查詢指定公鑰腳本對應的密鑰資訊*/
KeyStore
當前實現類有兩個:
- KeyStoreMemory:基於記憶體字典實現,沒有持久化能力,適合調試
- KeyStoreSql:基於Sql資料庫實現,適合作為生產環境密鑰存儲的參考實現
密鑰存儲實例的主要功能就是為ToolKit提供密鑰存儲和查詢能力。下面的程式碼使用KeyStoreSql來啟動ToolKit,生成幾個不同類型的地址,導入16進位私鑰和WIF私鑰,然後進行查詢:
ToolKit kit = new ToolKit( "testnet", new KeyStoreSqlite("testnet.wallet"), null,null,null ); String addr1 = kit.newAddress("SEGWIT-P2SH"); /*生成隔離見證p2sh地址*/ String addr2 = kit.newAddress("SEGWIT"); /*生成隔離見證地址*/ String addr3 = kit.newAddress("P2PKH"); /*生成P2PKH地址,默認選項*/ String addr4 = kit.addKey( /*導入16進位私鑰*/ "4aec8e45106....00d5c5a05b", "SEGWIT-P2SH" /*使用該私鑰的SEGWIT-P2SH地址*/ ); String addr5 = kit.addWif( /*導入WIF格式的私鑰*/ "cNJFgo1driF...SkdcF6JXXwHMm" ); /*默認使用私鑰的P2PKH地址*/ KeyStoreItem[] items = kit.list(); /*返回全部密鑰記錄*/ for(KeyStoreItem item:items) { System.out.printf("key => %sn",item.key); System.out.printf("wif => %sn",item.wif); System.out.printf("address => %sn",item.address); System.out.printf("script => %sn",item.script); } KeyStoreItem item = kit.getByAddress(addr1); /*查詢指定地址的密鑰記錄*/ System.out.printf("key => %sn",item.key);
(adsbygoogle = window.adsbygoogle || []).push({});