钱包开发经验分享:omniUSDT篇

编程

omniUSDT转账

参考代码:

    /**

* usdt 离线签名

*  @param privateKey:私钥

*  @param toAddress:接收地址

*  @param amount:转账金额

*  @return

*      

*/

public static String signTransaction(String fromAddress, String toAddress, String changeAddress, Long amount, boolean isMainNet, int propertyId, String privateKey) throws Exception {

List<UTXO> utxos = getUnspent(fromAddress, isMainNet);

// 获取手续费

Long fee = getFee(546L, utxos);

// 判断是主链试试测试链

NetworkParameters networkParameters = isMainNet ? MainNetParams.get() : TestNet3Params.get();

Transaction tran = new Transaction(networkParameters);

if (utxos == null || utxos.size() == 0) {

throw new Exception("utxo为空");

}

//这是比特币的限制最小转账金额,所以很多usdt转账会收到一笔0.00000546的btc

Long miniBtc = 546L;

tran.addOutput(Coin.valueOf(miniBtc), Address.fromBase58(networkParameters, toAddress));

//构建usdt的输出脚本 注意这里的金额是要乘10的8次方

String usdtHex = "6a146f6d6e69" + String.format("%016x", propertyId) + String.format("%016x", amount);

tran.addOutput(Coin.valueOf(0L), new Script(Utils.HEX.decode(usdtHex)));

Long changeAmount = 0L;

Long utxoAmount = 0L;

List<UTXO> needUtxo = new ArrayList<>();

//过滤掉多的uxto

for (UTXO utxo : utxos) {

if (utxoAmount > (fee + miniBtc)) {

break;

} else {

needUtxo.add(utxo);

utxoAmount += utxo.getValue().value;

}

}

changeAmount = utxoAmount - (fee + miniBtc);

//余额判断

if (changeAmount < 0) {

throw new Exception("utxo余额不足");

}

if (changeAmount > 0) {

tran.addOutput(Coin.valueOf(changeAmount), Address.fromBase58(networkParameters, changeAddress));

}

//先添加未签名的输入,也就是utxo

for (UTXO utxo : needUtxo) {

tran.addInput(utxo.getHash(), utxo.getIndex(), utxo.getScript()).setSequenceNumber(TransactionInput.NO_SEQUENCE - 2);

}

//下面就是签名

for (int i = 0; i < needUtxo.size(); i++) {

//这里获取地址

String addr = needUtxo.get(i).getAddress();

ECKey ecKey = DumpedPrivateKey.fromBase58(networkParameters, privateKey).getKey();

TransactionInput transactionInput = tran.getInput(i);

Script scriptPubKey = ScriptBuilder.createOutputScript(Address.fromBase58(networkParameters, addr));

Sha256Hash hash = tran.hashForSignature(i, scriptPubKey, Transaction.SigHash.ALL, false);

ECKey.ECDSASignature ecSig = ecKey.sign(hash);

TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false);

transactionInput.setScriptSig(ScriptBuilder.createInputScript(txSig, ecKey));

}

//这是签名之后的原始交易,直接去广播就行了

String signedHex = Hex.toHexString(tran.bitcoinSerialize());

//这是交易的hash

String txHash = Hex.toHexString(Utils.reverseBytes(Sha256Hash.hash(Sha256Hash.hash(tran.bitcoinSerialize()))));

return signedHex;

}

关于propertyId可以参考区块链浏览器,从区块链浏览器可以看到,USDT在omni协议中的propertyId是31。propertyId可以认为是omni协议实现的代币的主键,每个token的propertyId是不同的,而omniUSDT的propertyId是31。签名的结果是原始交易的Hex字符串,需要广播出去这笔交易才会被网络认可,广播交易可以用推送原始交易端点进行广播,有一些区块链浏览器也提供了广播交易的API,可以去翻找一下。

指定支付矿工费地址

在做代币归集的时候,代币支付的矿工费一般都是公链的币种,例如erc20USDT的转账是使用ETH支付矿工费的,而omniUSDT则是使用BTC支付矿工费,omniUSDT矿工费的支付在归集的面前成了一个棘手的难题。一般在做归集的时候会发生两次转账,一次是打入矿工费的转账,一次是归集的转账,而在omniUSDT归集的时候,这两次转账所支付的矿工费有时候是多余的,因为omniUSDT基于BTC公链,BTC公链本身就拥堵,矿工费又高,这样频繁归集来回打手续费,支出了太多不必要的矿工费。一般会有两个解决方案,一个是omniUSDT和BTC账户直接不做归集,需要的时候在手动归集,另一个设置一个打手续费的门槛和归集的门槛,当账户余额不大于某个阈值不考虑归集。其实还有第三种做法,指定支付矿工费地址,参考USDT钱包归集。

参考代码:

    /**

*

* @param privBtcKey 支付矿工费地址私钥

* @param btcAddress 支付矿工费地址

* @param privUsdtKey 支付USDT地址私钥

* @param usdtAddress 支付USDT地址

* @param recevieUsdtAddr 接收USDT地址

* @param amount 数量

* @param propertyId token唯一标识

* @param isMainNet 是否正式网络

* @return

*/

public static String createRawTransaction(String privBtcKey, String btcAddress, String privUsdtKey, String usdtAddress, String recevieUsdtAddr, long amount, int propertyId, boolean isMainNet) {

// 获取未花费列表

List<UTXO> unBtcUtxos = getUnspentFromTestNet(btcAddress);

List<UTXO> unUsdtUtxos = getUnspentFromTestNet(usdtAddress);

// 优化列表

Collections.sort(unBtcUtxos, (o1, o2) -> BigInteger.valueOf(o2.getValue().value).compareTo(BigInteger.valueOf(o1.getValue().value)));

List<UTXO> btcUtxos = new ArrayList<>();

List<UTXO> usdtUtxos = new ArrayList<>();

// 获取手续费

Long fee = getFee(546L, unBtcUtxos);

// 过滤输入

long btcTotalMoney = 0;

long usdtTotalMoney = 0;

for (UTXO us : unBtcUtxos) {

if (btcTotalMoney >= (1092L + fee))

break;

UTXO utxo = new UTXO(us.getHash(), us.getIndex(), us.getValue(), us.getHeight(), us.isCoinbase(), us.getScript());

btcUtxos.add(utxo);

btcTotalMoney += us.getValue().value;

}

for (UTXO us : unUsdtUtxos) {

if (usdtTotalMoney >= 546L)

break;

UTXO utxo = new UTXO(us.getHash(), us.getIndex(), us.getValue(), us.getHeight(), us.isCoinbase(), us.getScript());

usdtUtxos.add(utxo);

usdtTotalMoney += us.getValue().value;

}

// 判断是测试链还是正式链

NetworkParameters networkParameters = isMainNet ? MainNetParams.get() : TestNet3Params.get();

try {

if (!btcUtxos.isEmpty() && !usdtUtxos.isEmpty()) {

// find a btc eckey info

DumpedPrivateKey btcPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, privBtcKey);

ECKey btcKey = btcPrivateKey.getKey();

// a usdt eckey info

DumpedPrivateKey usdtPrivateKey = DumpedPrivateKey.fromBase58(networkParameters, privUsdtKey);

ECKey usdtKey = usdtPrivateKey.getKey();

// receive address

Address receiveAddress = Address.fromBase58(networkParameters, recevieUsdtAddr);

// create a transaction

Transaction tx = new Transaction(networkParameters);

// odd address

Address oddAddress = Address.fromBase58(networkParameters, btcAddress);

// 如果需要找零 消费列表总金额 - 已经转账的金额 - 手续费

long value_btc = btcUtxos.stream().mapToLong(x -> x.getValue().value).sum();

long value_usdt = usdtUtxos.stream().mapToLong(x -> x.getValue().value).sum();

// 总输入 - 手续费 - 546 -546 = 找零金额

long leave = (value_btc + value_usdt) - fee - 1092;

if (leave > 0) {

tx.addOutput(Coin.valueOf(leave), oddAddress);

}

String usdtHex = "6a146f6d6e69" + String.format("%016x", propertyId) + String.format("%016x", amount);

// usdt transaction

tx.addOutput(Coin.valueOf(546), new Script(Utils.HEX.decode(usdtHex)));

// send to address

tx.addOutput(Coin.valueOf(546), receiveAddress);

// create usdt utxo data

for (UTXO utxo : usdtUtxos) {

TransactionOutPoint outPoint = new TransactionOutPoint(networkParameters, utxo.getIndex(), utxo.getHash());

tx.addSignedInput(outPoint, utxo.getScript(), usdtKey, Transaction.SigHash.ALL, true);

}

for (UTXO utxo : btcUtxos) {

TransactionOutPoint outPoint = new TransactionOutPoint(networkParameters, utxo.getIndex(), utxo.getHash());

tx.addSignedInput(outPoint, utxo.getScript(), btcKey, Transaction.SigHash.ALL, true);

}

new Context(networkParameters);

tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);

tx.setPurpose(Transaction.Purpose.USER_PAYMENT);

return Hex.toHexString(tx.bitcoinSerialize());

}

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

测试代码:

	@Test

public void sendUsdtSpecifyMineAddress(){

String btcAddress = "mtESbeXpf5qYkbYnphhKEJ7FU3UyQKYQzy";

String btcPrivateKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

String usdtAddress = "mnvDACAJgny2yxxHNhud96sq9KRazbEeX2";

String usdtPrivateKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

String receiveAddress = "miiJAkDV4zfiZhSMwtiMCZr2rr4UecToMz";

String signTx = BtcUtil.createRawTransaction(btcPrivateKey, btcAddress, usdtPrivateKey, usdtAddress, receiveAddress, 100000000, 1, false);

String response = BtcUtil.sendTx(signTx, false);

logger.warn("response: {}", response);

}

测试结果:

这笔交易是在测试网络进行的,测试代币的获取可以通过向moneyqMan7uh8FqdCA2BV5yZ8qVrc9ikLP转入一些测试比特币,等待交易被确认就会获得对应的omni代币,兑换比例是1TBTC = 100OMNI。现在从这笔交易出发,可以看到这笔交易有两个输入,三个输出。两个输入分别是一个输入由一笔最小额度的BTC转账携带USDT转账,另一个输入则是支付手续费。

获取USDT余额

对于搭建了节点的朋友要获取token余额是一件很容易的事,直接调用RPC接口就可以获取,关于omni的RPC接口可以参看比特币核心omni api速查表 原,下面是使用第三方浏览器获取USDT余额的参考例子。

参考代码:

    /**

* 获取USDT余额

* @param address

* @return

*/

public static String getUsdtBalance(String address){

String url = "https://api.omniexplorer.info/v1/address/addr/";

OkHttpClient client = new OkHttpClient();

String content = "addr=" + address;

String response = null;

try {

response = client.newCall(new Request.Builder().url(url).post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), content)).build()).execute().body().string();

} catch (IOException e) {

e.printStackTrace();

}

JSONObject jsonObject = JSONObject.parseObject(response);

JSONArray balances = jsonObject.getJSONArray("balance");

List<Object> result = balances.parallelStream().filter(o -> JSONObject.parseObject(JSONObject.toJSONString(o)).getString("id").equals("31")).collect(Collectors.toList());

if(CollectionUtils.isEmpty(result)){

return BigDecimal.ZERO.toPlainString();

}

return new BigDecimal(JSONObject.parseObject(JSONObject.toJSONString(result.get(0))).getString("value")).divide(new BigDecimal("100000000")).toPlainString();

}

测试代码:

	@Test

public void testGetUsdtBalance(){

String address = "1Po1oWkD2LmodfkBYiAktwh76vkF93LKnh";

String balance = BtcUtil.getUsdtBalance(address);

logger.warn("balance: {}", balance);

}

上面例子里通过/v1/address/addr/details/接口查询的结果包括了所有的token余额,而omniUSDT的propertyid为31,propertyid是基于omni协议发布的代币的唯一字段,在omni协议层的发布的代币,目前只有omniUSDT使用比较广泛。

对我的文章感兴趣的话,请关注我的公众号

以上是 钱包开发经验分享:omniUSDT篇 的全部内容, 来源链接: utcz.com/z/512933.html

回到顶部