The OmniTool.Java development package is designed to rapidly increase the capability of supporting Omni/USDT digital assets for Java applications, including application scenarios that use their own Omni nodes, lightweight deployment scenarios based on third-party API services and offline naked transactions. The official download address: sc.hubwiz.com/codebag/omn… .

1. Overview of development kit

The OmniTool.Java development package contains the following features:

  • Complete Bitcoin/Omni Layer RPC API encapsulation
  • Supports the use of its own nodes or third-party services to obtain the specified address of the bitcoin UTXO collection
  • Support offline generation of Omni token or Bitcoin transfer naked transaction
  • Support to broadcast naked transactions using its own nodes or third-party services

Java supports locally deployed Omnicored nodes as well as open apis provided by third-party services. It is easy to add new third-party services by referring to code to implement the following interfaces:

  • IUtxoCollector: Utxo collector
  • IBroadcaster: Naked Deal broadcaster

The current version of OmniTool.Java software package is 1.0.0. The following figure shows the main classes/interfaces and their relationships:

The main code files of the OmniTool.Java software package are listed as follows:

Code files instructions
omnitool/build.gradle OmniTool.Java development library project build file
omnitool/src/main/java/omnitool/RpcClient.java OmniLayer/Bitcoin RPC API client implementation class
omnitool/src/main/java/omnitool/RpcRequest.java RPC API request class
omnitool/src/main/java/omnitool/RpcResponse.java RPC API response class
omnitool/src/main/java/omnitool/ToolKit.java Develop library entry classes
omnitool/src/main/java/omnitool/KeyStore.java Key store interface
omnitool/src/main/java/omnitool/UtxoCollector.java UTXO collector port
omnitool/src/main/java/omnitool/UtxoSelector.java UTXO selector interface
omnitool/src/main/java/omnitool/Broadcaster.java Bare transaction broadcast interface
omnitool/src/main/java/omnitool/KeyStoreMemory.java Memory key store implementation class
omnitool/src/main/java/omnitool/KeyStoreSql.java SQL database keystore implementation class
omnitool/src/main/java/omnitool/UtxoCollectorRpc.java UTXO collector implementation class based on RPC API
omnitool/src/main/java/omnitool/UtxoCollectorSmartbit.java UTXO collector implementation class based on cloud third-party API
omnitool/src/main/java/omnitool/UtxoSelectorDefault.java The default policy implementation class for the UTXO selector interface
omnitool/src/main/java/omnitool/BroadcasterRpc.java Naked transaction broadcast based on RPC API implementation class
omnitool/src/main/java/omnitool/BroadcasterSmartbit.java Based on cloud third party API naked transaction broadcast class
omnitool/src/main/java/omnitool/UtxoBag.java UTXO collection class
omnitool/src/main/java/omnitool/Utxo.java UTXO model class
omnitool/src/main/java/omnitool/KeyStoreItem.java Key store record class
omnitool/src/main/java/omnitool/Utils.java Auxiliary tools class
demo/build.gradle OmniTool.Java demo application project file
demo/src/App.java Demonstrate application entry code
demo/src/RpcClientDemo.java Omni/Btc RPC API client use method demo code
demo/src/KeyStoreMemoryDemo.java Memory key library use method demonstration code
demo/src/KeyStoreSqlDemo.java Sql key library use method demonstration code
demo/src/BtcTxRpcDemo.java Bitcoin transfer transaction demo code, based on RPC API
demo/src/BtcTxCloudDemo.java Bitcoin transfer transaction demo code, based on third-party service API
demo/src/OmniTxRpcDemo.java Omni/USDT token transfer transaction demo code, based on RPC API
demo/src/OmniTxCloudDemo.java Omni/USDT token transfer transaction demo code, based on third-party service API
demo/resources/log4j.properties Demonstrates the project log configuration file
build.gradle Root project build file
settings.gradle Root project configuration file

2, RpcClient class usage instructions

The RpcClient class encapsulates the RPC API protocol of Bitcoin and Omni Layer. When you create an RpcClient object, you pass in a node RPC URL that contains valid identity information. For example, suppose that the omnicORED node software installed on the local machine is connected to the main network, its configuration is as follows:

  • Rpcuser: user
  • Rpcpassword: 123456
  • Rpcport: 8332

You can instantiate RpcClient with the following code:

import omnitool.RpcClient;

RpcClient client = new RpcClient(
    "http://user:[email protected]:8332"/* Node RPC API URL*/);Copy the code

All RPC apis of both the Bitcoin and OMni layers can be called using the CALL () method of RpcClient. For example, use the listunspent call to get the UTXO at the specified address in the local node:

//import java.util.Map; Map[] unspents = client.call(Map[]. Class, /* Return result type */"listunspent", / * RPC API names * / 6, / * / 999999 * minimum confirmation, / * maximum confirmation number * / new String [] {"mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"} /* Address list */);for(Object unspent: unspents) {
  System.out.printf("txid: %s\n",(String)unspent.get("txid"));
}
Copy the code

The return result of the call() method corresponds to the result field in the JSON response of the RPC API, whose type depends on the first argument we pass in.

The first argument to the call() method declares a Class object of the result type returned by the method, which decodes the result field in the JSON response from the RPC API to the type specified by this argument. Usually we can use Map or Map[] to correspond to the contents of the Result field in the JSON response, as shown in the example above. This approach accommodates the changing RPC apis, but you have to be careful about type conversions when extracting data from the results.

The second argument to the call() method declares the name of the RPC API method to call, and the other arguments, starting with the third argument, represent the arguments to the specified RPC API method.

2.1 Define your own result class

Optionally, you can define your own class to make it easier to extract data from the return result of the call() method. For example, for the example above, we could define an Unspent class to describe the JSON object in the ListupSent response (you don’t need to define all the fields, you can choose according to your needs) :

class Unspent{
  public String txid;
  public long vout;
  public String account;
  public String scriptPubKey;
  public double amount;
  public long confirmations;
}
Copy the code

So we can call RpcClient as follows:

Unreturned [] unspents = client.call(unreturned []. Class, /* Return result type */"listunspent", /*RPC API method name */ 6, /* minimum confirmation */ 999999, /* maximum confirmation */ new String[]{"mgnucj8nYqdrPFh2JfZSB1NmUThUGnmsqe"} /* Address list */);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);
}
Copy the code

Obviously, defining your own result class can deserialize the JSON response of the RPC API directly to the specified type, which can be very helpful for manipulating complex response results. However, not only are the RPC apis for the Bitcoin and Omni layers dynamically evolving, but some JSON responses are inherently dynamic in structure, which often requires a combination of the more generic Map or Map[] types used earlier.

2.2 RPC API at Omni Layer

The OmniCore node extends an additional interface to manipulate Omni layer data in addition to Bitcoin’s original RPC interface. These extended RPC interfaces are distinguished from Bitcoin’s original RPC interface by the omni_ prefix.

For example, to get an address USDT tokens, balance need to use Omni omni_getbalance call, the following code to obtain the address 1 exodusjgwvnjzuykkxz4uhef77z6a5s4p USDT (31) asset ID: balance:

Item [] sub = sub (sub [].class, /* Returns the result type */"omni_getbalance"/*Omni RPC API method name */"1EXoDusjGwvnjZUyKkxZ4UHEf77z6A5S4P"/*Omni asset ID: USDT=31*/);for(Map b:balances){
  System.out.printf("balance: %s\n",(String)b.balance);
}
Copy the code

Similarly, you can use the omni_send call to perform a simple USDT transfer. For example, the following code to address from address 3 m9qvhktgarhqcmtm5crt9vaidj5psfqgy 37 fakponf7zqomlujeiko25pdiuvh5ylea into 100.0 USDT tokens:

String txID = client.call(string. class, /* Return result type */"omni_send"/*RPC API method name */"3M9qvHKtgARhqcMtM5cRT9VaiDJ5PSfQGY"/* The token is transferred to the address */"37FaKponF7zqoMLUjEiko25pDiuVH5YLEa", /* token transfer address */ 31, /* token ID: USDT*/"100.00"/* Number of tokens transferred */); System.out.printf("tx hash => %s\n",txid);
Copy the code

The demo/ rpCClientDemo. Java sample code in the development package uses RpcClient to fully demonstrate the Omni layer token issuance and transfer functions, if you plan to build your own Omni Core node, I believe this example will be very helpful.

3. Instructions for using ToolKit

If you don’t want to build your own Omni Core node and want to add Omni Layer/USDT support to your Java application based on third-party APIS, the easiest way is to use the offline transaction entry class ToolKit.

The main function of ToolKit is to create and broadcast Omni token or Bitcoin transfer naked transaction. Its basic steps are as follows:

  • Create an ToolKit instance
  • useAddKey()Method to add the necessary private key to the ToolKit instance, such as the roll-out address private key, because ToolKit needs the private key to sign the raw transaction
  • useSendOmnicoin()Method to generate and broadcast Omni token transfer naked transactions, or useSendBitcoin()Method to generate and broadcast naked bitcoin transfer transactions

3.1 Omni/USDT token transfer

The sample code of Omni/USDT token transfer using ToolKit is as follows, and is explained in the notes:

import omnitool.*;

String network = "main"; ToolKit kit = new ToolKit(network, /* Access network */ new KeyStoreMemory(), /* Use memory key store */ new UtxoCollectorSmartbit(network), /* Use the cloud Utxo collector */ new UtxoSelectorDefault(), /* Use the default policy Utxo selector */ new BroadcasterSmartbit(network) /* Use the cloud naked transaction broadcast */); String privHex ="4aec8e45106.... 00d5c5a05b"; /* Private key: hexadecimal string */ kit.addkey (privHex); /* Add private key to ToolKit*/ String from = kit.getKeystore ().getByKey(privHex).address; /* The address corresponding to the private key is used as the initiating account */ String to ="1GxX5tQR1C..... x2zbdj4mMuDcWR"; SendOmnicoin (from, /* sender address, private key must be added to wallet */ to, /* Receiver address */ 31, /* Transfer token ID, USDT=31*/ 10000 /* Transfer token number, Adjust to the minimum unit measurement of the integer */ NULL, /* Bitcoin processing fee payment address, the private key must have been added to ToolKit*/ 546, /* Circulating Bitcoin sent to the receiver, unit: SAToshi */ 1000, /* Transaction processing fee, unit: satoshi*/true/* Whether to broadcast */); System.out.printf("txid => %s\n",txid); /* Prints the transaction hash */Copy the code

Note:

  • The ToolKit example uses the private key in the wallet to generate an address list and uses these addresses to obtain UTXO information from third-party services. Therefore, the corresponding address of the private key in the wallet must have UTXO in the chain, so that the ToolKit object can successfully construct and sign the naked transaction.
  • The transfer destination address must be the same as the network specified when the Toolkit object is created, for example, the primary networkp2pkhAddress, the prefix should be1.

3.2 Specify the Omni transaction handling fee payment address

There are no transaction fees at the Omni protocol layer, but there are still transaction fees for bitcoin transactions embedded in Omni Transactions. When the sendOmnicoin() method’s pound-payment address is set to null, the bitcoin transaction fee is paid using the sender’s address. When your Java application needs to implement multi-account aggregation, it is easier to manage using a common processing payment address.

For example, the code USES the address below 35 stx1w6lkhj7hghz6gvnzxzcduhaeqdb6 Omni trading charges:

String txID = kit.sendOmnicoin(from, /* sender address, private key must be added to ToolKit*/ to, /* receiver address */ 31, /* Transfer OMNI token ID, 31: USDT*/ 10000, /* Transfer OMNI token quantity, has been adjusted to the minimum unit */"35stX1w6LKH... CdUhAeqDb6"/* Transaction fee payment address, the private key must have been added to ToolKit*/ 546, /* Circulating Bitcoin sent to the receiver, unit: satoshi*/ 1000, /* Transaction fee, unit: satoshi*/true/* Whether to broadcast */);Copy the code

Note:

  • Even if you specify a fee payment address with an adequate balance, the sender of an Omni transaction must still have a small bitcoin balance (546 SATOSHI), because the Omni protocol requires that the sender of the transaction have at least one UTXO available.
  • The processing address is also the change address to which excess bitcoins will be returned

3.3 Specify the number of Bitcoin transfers for Omni transactions

Since Omni transactions require the sender to have an available UTXO, in order for the address receiving the Omni token to continue circulating the Omni token held, the sendOmnicoin() method needs to transfer at least 546 SATOSHI of Bitcoin to the recipient’s address, which can be changed when the method is called.

For example, the following code transfers 1000 SATOSHI to the receiver:

String txID = kit.SendOmnicoin(from, /* sender address, private key must be added to ToolKit*/ to, /* receiver address */ 31, /* Transfer OMNI token ID, 31: USDT*/ 10000, /* Transfer OMNI token number has been adjusted to the minimum unit */ fundAddr:"35stX1w6LKH... CdUhAeqDb6"/* Transaction fee payment address, the private key must have been added to ToolKit*/ 1000, /* Circulating Bitcoin sent to the receiver, unit: satoshi*/ 1000, /* Transaction fee, unit: satoshi*/true/* Whether to broadcast */);Copy the code

3.4 Specify Omni transaction fees

The sendOmnicoin() method can set the transaction fee, for example 3000 SATOSHI:

String txID = kit.SendOmnicoin(from, /* sender address, private key must be added to ToolKit*/ to, /* receiver address */ 31, /* Transfer OMNI token ID, 31: USDT*/ 10000, /* Transfer OMNI token number */ fundAddr:"35stX1w6LKH... CdUhAeqDb6"/* Transaction fee payment address, the private key must have been added to ToolKit*/ 1000, /* Circulating Bitcoin sent to the receiver, unit: satoshi*/ 3000, /* Transaction fee, unit: satoshi*/true/* Whether to broadcast */);Copy the code

3.5 Only Omni naked transactions are generated but not broadcast

Sometimes it is possible to just generate the Omni transfer naked transaction without broadcasting it out. You can cancel broadcasting by setting the last parameter of the sendOmnicoin() method to false, and the generated naked transaction will be returned. Such as:

String rawtx = kit.SendOmnicoin(from, /* Sender address, private key must be added to ToolKit*/ to, /* Receiver address */ 31, /* Transfer OMNI token ID, 31: USDT*/ 10000, /* Transfer OMNI token number has been adjusted to the minimum unit */ fundAddr:"35stX1w6LKH... CdUhAeqDb6"/* Transaction fee payment address, the private key must have been added to ToolKit*/ 1000, /* Circulating Bitcoin sent to the receiver, unit: satoshi*/ 3000, /* Transaction fee, unit: satoshi*/false/* Whether to broadcast */); System.out.println(rawtx); /* Print the naked transaction content */Copy the code

3.6 Bitcoin transfer

OmniTool.Java also supports the generation and broadcasting of naked bitcoin transfer transactions.

For example, the following code transfers 10000 SATOSHI from one address to another address in ToolKit:

String privHex = "4aec8e45106.... 00d5c5a05b"; /* Private key: hexadecimal string */ kit.addkey (privHex); /* Add private key to ToolKit*/ String from = kit.getKeystore ().getByKey(privHex).address; /* The address corresponding to the private key is used as the initiating account */ String to ="1GxX5tQR1C..... x2zbdj4mMuDcWR"; /* Txid = kit.sendbitcoin (from, /* sender address */ to, /* receiver address */ 10000, /* Number of bitcoins transferred, unit: SATOSHI*/ 1500, /* handling fee, unit: SATOSHI*/ null, /* Change address */true/* Whether to broadcast */);Copy the code

When the change address is set to NULL, the SendBitcoin() method uses the sender address as the change address. The following code creates a new address to receive change:

String changeAddr = kit.newAddress(); /* Create a new address */ String txid = kit.sendbitcoin (from, /* sender address */ to, /* receiver address */ 10000, /* Number of bitcoins transferred, unit: SATOSHI*/ 1500, /* processing fee, unit: SATOSHI*/ changeAddr, /* Change address */true/* Whether to broadcast */);Copy the code

Similarly, you can set the last parameter to false when you only want to generate naked transactions and do not want to broadcast them.

4. UTXO collector

OmniTool.Java uses the UtxoCollector interface to specify the UTXO collection function. The implementation of this interface needs to support the collection of candidate UTXOs for the specified address. Multiple addresses can be specified.

Interface methods:

UtxoBag collect(String[] addresses); /* Extract and return a collection of candidate UTXO */Copy the code

The addresses parameter addresses specifies the list of UTXO addresses to be collected.

Current implementation class:

  • UtxoCollectorSmartbit: Utxo collector implemented based on third-party cloud API
  • UtxoCollectorRpc: Utxo collector based on RPC API of omnicored node

For example, the following code uses UtxoCollectorSmartbit to get the UTXO at a specified address of the test chain:

UtxoCollector collector = new UtxoCollectorSmartbit(
    "main"/* mainchain */); String[] addresses = new String[]{"1C3TZ... brS2xHM"}; UtxoBag collected = collector.collect (addresses /* address list */);Copy the code

5. UTXO selector

Omnitool. Java uses the interface UtxoSelector to specify UTXO filtering policies. Implementation of this interface requires selecting available UTXOs from candidate UTXOs based on the target amount and returning a new UtxoBag instance.

Interface methods:

UtxoBag select(long target,UtxoBag collected); /* Select consumable UTXO, return UtxoBag object */Copy the code

The target parameter specifies the minimum amount of money to be achieved, in SATOSHI.

The argument collected is a collection of candidate UTXO, usually the result returned by a call to collect() from UtxoCollector.

Current implementation class:

  • UtxoSelectorDefault: Select at least six confirmed unconsumed UTXOs

For example, the following code uses the UtxoSelectorDefault instance to select utXOs of at least 100000 SATOSHI from the candidate UTXOs:

//collected represents a collection of candidate UTXOs. Call result from COLLECT () of the UTXO collector UtxoSelector selector = new UtxoSelectorDefault(); UtxoBag selected = selector. Select (100000, /* minimum target amount */ collected /* candidate UTXO collection */); System.out.printf("total:%d\n":selected.getTotal()); /* Print out the utXO total */Copy the code

Considering the indivisibility of UTXOs, the sum of selected UTXOs may exceed the target amount. You can view the total UTXOs in the collection using the getTotal() method of the UtxoBag instance, as shown above.

6. Naked trade broadcast

OmniTool.Java uses the Broadcaster interface to dictate the functional specification of naked transaction broadcasting. The implementation of this interface should broadcast naked transactions into the Omni/Btc network.

Interface methods:

String broadcast(String rawtx); /* Broadcast naked transaction */Copy the code

The rawtx argument is used to declare the raw transaction to be broadcast as a hexadecimal string.

Current implementation class:

  • BroadcasterSmartbit
  • BroadcasterRpc

For example, the following code uses BroadcasterSmartbit to broadcast the raw transaction code stream to the Omni/Btc network:

Broadcaster broadcaster = new BroadcasterSmartbit(
    "testnet"/* Test chain */); String txid = broadcaster.broadcast("01000000011da9283b4... 59f58488ac00000000"/* naked transaction */);Copy the code

7. Key storage interface

Omnitool. Java specifies the function specifications of the KeyStore using the KeyStore.

Interface methods:

bool add(KeyStoreItem item); /* Store key */ KeyStoreItem[] list(); /* View all keys */ KeyStoreItem getByKey(); /* Query the key information corresponding to the specified hexadecimal private key */ KeyStoreItem getByWif(a); KeyStoreItem getByAddress(); KeyStoreItem getByAddress(); /* Query the key information corresponding to the specified address */ KeyStoreItem getByScript(); /* Query the key information corresponding to the specified public key script */Copy the code

KeyStore currently implements two classes:

  • KeyStoreMemory: Based on memory dictionary implementation, no persistence capability, suitable for debugging
  • KeyStoreSql: An implementation based on Sql databases. It is suitable for reference implementation of key stores in production environments

The key store instance provides key storage and query capabilities for ToolKit. The following code uses KeyStoreSql to start the ToolKit, generate several different types of addresses, import the hexadecimal private key and WIF private key, and then query:

ToolKit kit = new ToolKit(
    "testnet",
    new KeyStoreSqlite("testnet.wallet"),
    null,null,null
  );

String addr1 = kit.newAddress("SEGWIT-P2SH"); /* Generate isolated witness p2sh address */ String addr2 = kit."SEGWIT"); /* Generate isolated witness address */ String addr3 = kit.newAddress("P2PKH"); /* Generate P2PKH address, default option */ String addr4 = kit.addkey (/* import hexadecimal private key */)"4aec8e45106.... 00d5c5a05b"."SEGWIT-P2SH"/* SEGWIT -p2sh address to use the private key */); String addr5 = kit.addWif(/* Import the private key in WIF format */"cNJFgo1driF... SkdcF6JXXwHMm"); /* Default private key P2PKH address */ KeyStoreItem[] items = kit.list(); /* Return all key records */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);  
Copy the code

Download address: Omni/USDT Java development package – Huizhi network