常量请参考https://blog.csdn.net/liu1765686161/article/details/79655332
/**
* usdt 离线签名 并 发送交易
*
* @param fromAddress
* :转出地址
* @param toAddress
* :接收地址
* @param changeAddress
* : 找零地址
* @param value
* :转账金额
* @return hash
*/
public String rawSignAndSend(String fromAddress, String toAddress,
String changeAddress, double value) throws Exception {
List utxos = new ArrayList();
if (mainAddress.equals(fromAddress)) {
utxos = this.getUnspent(fromAddress);
} else {
utxos = this.getUnspent(fromAddress, toAddress);
}
// 转账金额 格式转化
Long amount = BigDecimalUtil.inputConvert(value);
// 获取手续费
Long fee = this.getOmniFee(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 needUtxo = new ArrayList();
// 过滤掉多的utxo
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);
}
//解锁钱包
if (!this.walletpassphrase()) {
log.error("解锁钱包失败");
}
// 下面就是签名
for (int i = 0; i < needUtxo.size(); i++) {
String addr = needUtxo.get(i).getAddress();
String privateKey = this.getPrivateKey(addr);
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());
log.info("签名之后的原始交易:{}", signedHex);
// 这是交易的hash
String txHash = Hex.toHexString(Utils.reverseBytes(Sha256Hash
.hash(Sha256Hash.hash(tran.bitcoinSerialize()))));
log.info("交易数据fee:{},utxoAmount:{},changeAmount:{},hash:{}", fee,
utxoAmount, changeAmount, txHash);
JSONObject json = doRequest("sendrawtransaction", signedHex);
if (isError(json)) {
log.error("发送交易失败");
return null;
} else {
String result = json.getString("result");
log.info("发送成功 hash:{}", result);
return result;
}
}
public String getPrivateKey(String address) {
JSONObject json = doRequest("dumpprivkey", address);
if (isError(json)) {
log.error("获取私钥失败:", address);
return null;
} else {
return json.getString("result");
}
}
/**
* 获取矿工费用
*
* @param utxos
* @return
*/
public Long getOmniFee(List utxos) {
Long miniBtc = 546L;
Long feeRate = getFeeRate();
Long utxoAmount = 0L;
Long fee = 0L;
Long utxoSize = 0L;
for (UTXO output : utxos) {
utxoSize++;
if (utxoAmount >= (fee + miniBtc)) {
break;
} else {
utxoAmount += output.getValue().value;
fee = (utxoSize * 148 + 34 * 3 + 10) * feeRate;
}
}
return fee;
}
/***
* 获取未消费列表
*
* @param address
* :地址 地址顺序很重要
* @return
*/
public List getUnspent(String... address) {
List utxos = Lists.newArrayList();
try {
/**
* { "txid":
* "62906596e41aa041fadfb1f0cfb531137603ab1670515de28028b201dbf55bf5"
* , "vout": 1, "address": "n2RXasuis8pCQScDQjDV5vhaR7pBCvNUnB",
* "account": "btc2", "scriptPubKey":
* "76a914e553ef5218b38b86d1d4670dd4bd65d270f28d6e88ac", "amount":
* 0.00010000, "confirmations": 127231, "spendable": true,
* "solvable": true }
*/
JSONObject jsonObject = doRequest("listunspent", 0,
99999999, address);
JSONArray outputs = jsonObject.getJSONArray("result");
if (outputs == null || outputs.size() == 0) {
System.out.println("交易异常,余额不足");
}
for (int i = 0; i < outputs.size(); i++) {
JSONObject outputsMap = outputs.getJSONObject(i);
String tx_hash = outputsMap.getString("txid");
String scriptPubKey = outputsMap.getString("scriptPubKey");
Double amount = outputsMap.getDouble("amount");
Long value = BigDecimalUtil.inputConvert(amount);
String vout = outputsMap.getString("vout");
String addr = outputsMap.getString("address");
UTXO utxo = new UTXO(Sha256Hash.wrap(tx_hash),
Long.valueOf(vout), Coin.valueOf(Long.valueOf(value)),
0, false, new Script(Hex.decode(scriptPubKey)), addr);
System.out.println(utxo.getAddress());
utxos.add(utxo);
}
return utxos;
} catch (Exception e) {
log.error("【BTC获取未消费列表】失败,", e);
return null;
}
}
/**
* 获取btc费率
*
* @return
*/
public Long getFeeRate() {
try {
String re = HttpUtil
.get("https://bitcoinfees.earn.com/api/v1/fees/recommended");
JSONObject json = JSON.parseObject(re);
return Long.valueOf(json.get("fastestFee").toString());
} catch (Exception e) {
e.printStackTrace();
return 12L;
}
}