天天看點

用OmniTool.Java 高效開發Omni/USDT對接應用

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 高效開發Omni/USDT對接應用

OmniTool.Java軟體包主要代碼檔案清單如下:

代碼檔案 說明
omnitool/build.gradle OmniTool.Java開發庫項目建構檔案
omnitool/src/main/java/omnitool/RpcClient.java OmniLayer/Bitcoin RPC API用戶端實作類
omnitool/src/main/java/omnitool/RpcRequest.java RPC API請求類
omnitool/src/main/java/omnitool/RpcResponse.java RPC API響應類
omnitool/src/main/java/omnitool/ToolKit.java 開發庫入口類
omnitool/src/main/java/omnitool/KeyStore.java 密鑰存儲接口
omnitool/src/main/java/omnitool/UtxoCollector.java UTXO采集器接口
omnitool/src/main/java/omnitool/UtxoSelector.java UTXO選擇器接口
omnitool/src/main/java/omnitool/Broadcaster.java 裸交易廣播器接口
omnitool/src/main/java/omnitool/KeyStoreMemory.java 記憶體密鑰存儲庫實作類
omnitool/src/main/java/omnitool/KeyStoreSql.java SQL資料庫密鑰存儲庫實作類
omnitool/src/main/java/omnitool/UtxoCollectorRpc.java 基于RPC API的UTXO采集器實作類
omnitool/src/main/java/omnitool/UtxoCollectorSmartbit.java 基于雲端第三方API的UTXO采集器實作類
omnitool/src/main/java/omnitool/UtxoSelectorDefault.java UTXO選擇器接口的預設政策實作類
omnitool/src/main/java/omnitool/BroadcasterRpc.java 基于RPC API的裸交易廣播器實作類
omnitool/src/main/java/omnitool/BroadcasterSmartbit.java 基于雲端第三方API的裸交易廣播器實作類
omnitool/src/main/java/omnitool/UtxoBag.java UTXO集合類
omnitool/src/main/java/omnitool/Utxo.java UTXO模型類
omnitool/src/main/java/omnitool/KeyStoreItem.java 密鑰存儲記錄類
omnitool/src/main/java/omnitool/Utils.java 輔助工具類
demo/build.gradle OmniTool.Java示範應用項目檔案
demo/src/App.java 示範應用入口代碼
demo/src/RpcClientDemo.java Omni/Btc RPC API用戶端使用方法示範代碼
demo/src/KeyStoreMemoryDemo.java 記憶體密鑰庫使用方法示範代碼
demo/src/KeyStoreSqlDemo.java Sql密鑰庫使用方法示範代碼
demo/src/BtcTxRpcDemo.java 比特币轉賬交易示範代碼,基于RPC API
demo/src/BtcTxCloudDemo.java 比特币轉賬交易示範代碼,基于第三方服務API
demo/src/OmniTxRpcDemo.java Omni/USDT代币轉賬交易示範代碼,基于RPC API
demo/src/OmniTxCloudDemo.java Omni/USDT代币轉賬交易示範代碼,基于第三方服務API
demo/resources/log4j.properties 示範項目日志配置檔案
build.gradle 根項目建構檔案
settings.gradle 根項目配置檔案

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: %s\n",(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: %s\n",unspent.txid);
  System.out.printf("vout: %d\n",unspent.vout);
  System.out.printf("amount:%f\n",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: %s\n",(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 => %s\n",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 => %s\n",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選擇器

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:%d\n":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、密鑰存儲接口

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 => %s\n",item.key);
  System.out.printf("wif => %s\n",item.wif);
  System.out.printf("address => %s\n",item.address);
  System.out.printf("script => %s\n",item.script);
}  
  
KeyStoreItem item = kit.getByAddress(addr1);          /*查詢指定位址的密鑰記錄*/
System.out.printf("key => %s\n",item.key);             

下載下傳位址:

Omni/USDT Java開發包 - 彙智網