OmniTool.Java开发包适用于为Java应用快速增加对Omni/USDT数字资产的支持能力, 即支持使用自有Omni节点的应用场景,也支持基于第三方API服务和离线裸交易 的轻量级部署场景。OmniTool.Java官方下载地址:Omni/USDT JAVA开发包。
OmniTool.Java开发包主要包含以下特性:
OmniTool.Java支持本地部署的Omnicored节点,也支持第三方服务提供的开放API, 要增加新的第三方服务也非常简单,只需要参考代码实现如下接口:
OmniTool.Java软件包当前版本1.0.0,主要类/接口及关系如下图所示:
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 | 根项目配置文件 |
RpcClient类封装了比特币以及Omni Layer的RPC API接口协议。创建RpcClient对象时,需要传入 包含有效身份信息的节点RPC URL。例如,假设安装在本机的omnicored节点软件接入主网,其配置如下:
那么可以使用如下的代码来实例化RpcClient:
import omnitool.RpcClient; RpcClient client = new RpcClient( "http://user:123456@127.0.0.1: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方法的参数。
可选地,也可以自己定义一个类来简化从 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[]
类型。
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节点,相信这个示例会有很大帮助。
如果不愿意搭建自己的Omni Core节点,而是希望基于第三方API为自己的Java应用增加 对Omni Layer/USDT的支持,那么最简单的方法是使用离线交易的入口类 ToolKit 。
ToolKit类的主要作用是创建并广播Omni代币或比特币转账裸交易, 它的基本使用步骤如下:
AddKey()
方法将必要的私钥加入该ToolKit实例,例如转出地址的私钥,因为ToolKit需要利用私钥对裸交易进行签名 SendOmnicoin()
方法生成并广播Omni代币转账裸交易,或者使用 SendBitcoin()
方法生成并广播比特币转账裸交易 使用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); /*打印交易哈希*/
注意:
p2pkh
地址,前缀应当为 1
。 在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交易要求发送方必须有可用的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 /*是否广播*/ );
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 /*是否广播*/ );
有时可能只需要生成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); /*打印裸交易内容*/
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
。
OmniTool.Java使用接口 UtxoCollector
来约定UTXO的采集功能。 该接口的实现需要支持获取指定地址的候选UTXO集合,可指定多个地址。
接口方法:
UtxoBag collect(String[] addresses); /*提取并返回候选UTXO集合*/
参数 addresses
用来声明要收集UTXO的地址清单。
当前实现类:
例如,下面的代码使用UtxoCollectorSmartbit获取测试链某个指定地址 的UTXO:
UtxoCollector collector = new UtxoCollectorSmartbit( "main" /*主链*/ ); String[] addresses = new String[]{"1C3TZ...brS2xHM"}; UtxoBag collected = collector.Collect( addresses /*地址清单*/ );
OmniTool.Java使用接口 UtxoSelector
来约定UTXO的筛选策略。该接口的实现需要 根据目标金额从候选UTXO中选择可用UTXO,并返回新的UtxoBag实例。
接口方法:
UtxoBag select(long target,UtxoBag collected); /*选择可消费UTXO,返回UtxoBag对象*/
参数 target
声明要达成的最低金额目标,单位:SATOSHI。
参数 collected
是候选的utxo集合,通常是UtxoCollector的 collect()
调用 返回的结果。
当前实现类:
例如下面的代码使用 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总额,如上。
OmniTool.Java使用 Broadcaster
接口约定裸交易广播的功能规格。该接口的实现 应当将裸交易广播到Omni/Btc网络中。
接口方法:
String broadcast(String rawtx); /*广播裸交易*/
参数 rawtx
用来声明要广播的裸交易,类型为16进制字符串。
当前实现类:
例如,下面的代码使用 BroadcasterSmartbit
将裸交易码流广播到Omni/Btc网络中:
Broadcaster broadcaster = new BroadcasterSmartbit( "testnet" /*测试链*/ ); String txid = broadcaster.broadcast( "01000000011da9283b4...59f58488ac00000000" /*裸交易*/ );
OmniTool.Java使用 KeyStore
约定密钥存储的功能规格。
接口方法:
bool add(KeyStoreItem item); /*存入密钥*/ KeyStoreItem[] list(); /*浏览全部密钥*/ KeyStoreItem getByKey(); /*查询指定16进制私钥对应的密钥信息*/ KeyStoreItem getByWif(); /*查询指定WIF格式私钥对应的密钥信息*/ KeyStoreItem getByAddress(); /*查询指定地址对应的密钥信息*/ KeyStoreItem getByScript(); /*查询指定公钥脚本对应的密钥信息*/
KeyStore
当前实现类有两个:
密钥存储实例的主要功能就是为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);
OmniNet.Java下载地址:http://sc.hubwiz.com/codebag/omni-java-lib/