export default class BitcoinBase {
generateOmniUSDTOutputopReturnScript(amount) {
/**
* 功能:生成基于【omni-USDT】的【OP_RETURN】脚本!
*/
return OwnoriginalBitcoinBase.generateOutputopReturnScript(this.generateOmniUSDTOutputScriptData(amount));
}
generateOmniUSDTOutputScriptData(amount) {
/**
* 功能:生成创建OMNI-USDT的交易脚本的数据Data
* 特殊说明:
* 1、基于OMNI-USDT-Mainnet网络的propertyID是31【十六进制为000000000000001f】
* 2、基于OMNI-USDT-Testnet网络的propertyID是1
* 举例:
* 基于OMNI-USDT-Mainnet网络案例:
* 案例:6a14【OP_RETURN】6f6d6e69【USDT固定值段】000000000000001f【31】000000001dcd6500【5】
* 注意:
* 1、其中【OP_RETURN】后的就是Data部分!
* 2、为何【USDT固定值段】为6f6d6e69,可以随意查询一个基于omni-USDT的交易记录查看outputs的OP_RETURN数据!
* https://www.omniexplorer.info/block/750831
* https://www.blockchain.com/btc/tx/f667bcdc9f547d8a4b7b5ef5e69b48a07ee6d7d850dc0f2bf729a00eef8131b4
*/
let _omniUSDTPropertyID;
let _omniUSDTFixedHex;
if (this.networkNodeType === "mainnet") {
/**
* 1、Omni-USDT固定值段【这个固定值-mainnet和testnet的值未必是一样的,需核对测试...】
*/
/**
* 1、OMNI-USDT-Mainnet网络的propertyID是31【十六进制为000000000000001f】
* 2、Bitcoin主网的代表属性ID参考:https://blockchair.com/zh/bitcoin/omni/property/31
* 3、OMNI-USDT-Mainnet网络转账时固定值段为【6f6d6e69】
*/
_omniUSDTPropertyID = 31;
_omniUSDTFixedHex = "6f6d6e69";
} else {
/**
* 1、OMNI-USDT-Testnet网络的propertyID是1。
* 2、OMNI-USDT-Testnet网络固定值段是否和主网一致【需核对测试】暂时测试网络转账USDT是没有成功的!
*/
throw new Error(`${this.prefixStr}暂不支持networkNodeType=${this.networkNodeType}的USDT代币测试交易` + `【暂时没有寻找到第三方可测试USDT交易的节点API】`);
}
let _omniUSDTScriptData = Buffer.from([_omniUSDTFixedHex, OwnoriginalBitcoinBase.autoFullPrefixZero(Number(_omniUSDTPropertyID).toString(16), 16), OwnoriginalBitcoinBase.autoFullPrefixZero(BitcoinNetworkUtils.toSatoshisAmount(amount, true), 16)].join(""), "hex");
console.log(`${this.prefixStr}【Omni-USDT】的输出脚本为:`, _omniUSDTScriptData);
return _omniUSDTScriptData;
}
static autoFullPrefixZero(value, allow_max_length = null) {
/**
* 功能:自动前缀补0,直到value最大长度为allow_max_length
* 参数:
* allow_max_length:如为null,默认为16位!
*/
allow_max_length = allow_max_length || 16;
let _valueString = value.toString();
let _valueList = Array.from(_valueString);
for (let i = 0; i < allow_max_length - _valueString.length; i++) {
_valueList.unshift("0");
}
return _valueList.join("");
}
static generateOutputopReturnScript(data) {
/**
* 功能:封装【bitcoinjs-lib】生成输出【OP_RETURN】脚本数据
*/
return bitcoin.script.compile([bitcoin.opcodes.OP_RETURN, Buffer.from(data)]);
}
}
基于【bitcoinjs-lib】的封装交易方法
import bitcoin from "bitcoinjs-lib";
/**
* 实战环境温馨提示:
* 1、基于【bitcoinjs-lib】转账交易封装比较复杂,更适合了解Bitcoin交易的原理【自己封装可能存在各种小Bug】
* 2、基于【bitcore-lib】转账交易是bitpay官方封装,简单易用,更适合生产环境!
*/
export default class Bitcoinjs extend BitcoinBase {
async sendBitcoinNetworkTransactionCommon(token_type, utxo_inputs, to, fee_satoshis, to_btc_satoshis_amount, amount, total_btc_satoshis_balance) {
/**
* 功能:重写父类方法【基于原生的bitcoinjs-lib的转账方法】
* 基于【bitcoinjs-lib】对比【bitcore-lib】:都是基于离线签名【绝对安全】都是基于【MIT】发布的证书
* a、基于【bitcoinjs-lib】
* 1、封装比较复杂,基于各种Address的特性,input需要携带不一样的参数!
* 2、转账Omni-USDT未完成确认之前,转账全部BTC余额是无法广播成功【只能转账非参与USDT的BTC部分】
* b、基于【bitcore-lib】一切问题都是自动解决了,因为bitpay官方内部已做好一切封装,直接调用即可!
*/
const psbt = new bitcoin.Psbt({ network: this.originalBitcoinNetworkNode });
for (const _utxo_input of utxo_inputs) {
/**
* 1、基于【bitcoinjs-lib】的执行转出的wallet地址,必须根据地址的特性配置一些特殊参数!
* 2、请参考官方案例:https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts
*/
psbt.addInput({
hash: _utxo_input.txId,
index: _utxo_input.outputIndex,
...await this.__getAddressInputNonorWitnessUtxoObject(_utxo_input),
...this.__getAddressRedeemOrWitnessScriptObject(),
});
}
if (token_type === "usdt") {
/**
* 1、这里的【_transaction.addData(string)会自动生成【OP_RETURN】格式的数据【等价于new bitcore.Script.buildDataOut(string)】
* 2、参考官方文档:https://github.com/bitpay/bitcore-lib/blob/master/docs/examples.md#create-an-op-return-transaction
*/
psbt.addOutput({
script: Buffer.from(this.generateOmniUSDTOutputopReturnScript(amount)), value: 0,
});
}
psbt.addOutput({
address: to, value: to_btc_satoshis_amount,
});
let _remainingBTCBalanceSatoshis = total_btc_satoshis_balance - to_btc_satoshis_amount - fee_satoshis;
if (_remainingBTCBalanceSatoshis > 0) {
/**
* 1、【非常重要】每次交易当前Wallet剩余的余额必须再次转给自己【change找零地址】否则剩余余额部分会全部自动变成手续费fee消耗了【直接挂逼!!!】
* 2、比特币交易原理:
* a、取出全部可用【UtxoInput】余额,然后一次性分配给【toAddress金额】+【剩余的数量转入找零change地址】+【fee手续费】
* b、change找零地址,通常为当前转出钱包地址
* c、【务必注意】如果【未提供change找零地址】则全部可用【UtxoInput】余额自动分配给【toAddress金额】+【剩余的数量全部自动当fee手续费消耗了】
*/
psbt.addOutput({
address: this.walletAddress, value: _remainingBTCBalanceSatoshis,
});
}
// 签名全部inputs
psbt.signAllInputs(this.keyPair);
// 序列化全部inputs
psbt.finalizeAllInputs();
return this.broadcastTx(psbt.extractTransaction().toHex(), token_type, to, to_btc_satoshis_amount, amount);
}
async __getAddressInputNonorWitnessUtxoObject(utxo_input) {
/**
* 功能:获取是否隔离见证地址的utxo特殊参数对象
* 特殊说明:
* 1、基于是否隔离见证地址,input传入的Utxo参数不一样!
* 2、如果未提供对应的nonWitnessUtxo或witnessUtxo参数,签名会抛异常【Error: No inputs were signed】
*/
if (this.addresstype === "prefix_1") {
/**
* 1、转出Address如为非隔离见证【1开头地址】则必填参数nonWitnessUtxo【非隔离见证输入现在需要将整个前一个tx作为缓冲区传递】
*/
return this.__getAddressNonWitnessUtxoObj(utxo_input);
}
return {
/**
* 1、转出Address如为隔离见证【bc1或3开头地址】则必填参数witnessUtxo【只需要Utxo的scriptPubkey和value】
*/
witnessUtxo: {
script: Buffer.from(utxo_input.script, "hex"), value: utxo_input.satoshis,
},
};
}
async __getAddressNonWitnessUtxoObj(utxo_input) {
/**
* 功能:获取已完成的utxo_input【等价于上一个prevIoUs】交易的tx_hex的Buffer对象【non-segwit inputs Now require passing the whole prevIoUs tx as Buffer】
* 特殊说明:
* 1、注:每一个utxo_input相比【当前即将执行的转账交易来说】都是【prevIoUs】
*/
return {
nonWitnessUtxo: Buffer.from(
(await this.getTx(utxo_input.txId)).tx_hex, "hex"),
};
}
__getAddressRedeemOrWitnessScriptObject() {
/**
* 功能:获取address匹配的redeemScript或witnessScript对象!
*/
let _addressObj = this.getWalletAddress(true);
/**
* 1、address基于P2SH方式生成,则必须redeemScript
* 2、address基于P2WSH方式生成,则必须witnessScript
* 3、address基于其他方式生成,则以上2个参数都不需要!
*/
if (_addressObj.redeem) {
return {
redeemScript: _addressObj.redeem.output,
};
}
if (_addressObj.witness) {
return {
witnessScript: _addressObj.witness.output,
};
}
return {};
}
}
基于【bitcore-lib】的封装交易方法
import bitcore from "bitcore-lib";
/**
* 参考外国人写的案例【基于bitpay的bitcore-lib的案例】:https://blog.logrocket.com/sending-bitcoin-with-javascript/
*/
export default class Ownbitpaybitcore extends BitcoinBase {
async sendBitcoinNetworkTransactionCommon(token_type, utxo_inputs, to, fee_satoshis, to_btc_satoshis_amount, amount, total_btc_satoshis_balance) {
/**
* 功能:重写父类方法【基于bitpay的bitcore-lib的转账方法】
*/
// 这里创建交易对象无需参数,这里默认传入一个[],为消除警告!
const _transaction = new bitcore.Transaction(...[]);
_transaction.from(utxo_inputs);
if (token_type === "usdt") {
/**
* 1、这里的【_transaction.addData(string)会自动生成【OP_RETURN】格式的数据【等价于new bitcore.Script.buildDataOut(string)】
* 2、参考官方文档:https://github.com/bitpay/bitcore-lib/blob/master/docs/examples.md#create-an-op-return-transaction
*/
_transaction.addData(this.generateOmniUSDTOutputScriptData(amount));
}
_transaction.to(to, to_btc_satoshis_amount);
/**
* 1、【非常重要】每次交易当前Wallet剩余的余额必须再次转给自己【change找零地址】否则剩余余额部分会全部自动变成手续费fee消耗了【直接挂逼!!!】
* 2、比特币交易原理:
* a、取出全部可用【UtxoInput】余额,然后一次性分配给【toAddress金额】+【剩余的数量转入找零change地址】+【fee手续费】
* b、change找零地址,通常为当前转出钱包地址
* c、【务必注意】如果【未提供change找零地址】则全部可用【UtxoInput】余额自动分配给【toAddress金额】+【剩余的数量全部自动当fee手续费消耗了】
*/
_transaction.change(this.walletAddress);
// 扣除手续费
_transaction.fee(fee_satoshis);
// 私钥签名【sign有三个参数,默认仅需传入第一个即可,后面站位已消除警告】
_transaction.sign(this.walletPrivateKey, ...[]);
return this.broadcastTx(
_transaction.serialize(), token_type, to, to_btc_satoshis_amount, amount);
};
}
参考文档案例:
1、【使用 Bitcoinjs、Bitcoin Core 和 LND 进行比特币编程【】Bitcoin Programming with BitcoinJS, Bitcoin Core and LND :: Bitcoin Programming with BitcoinJS, Bitcoin Core and LND
2、【】Wallet development experience sharing: omniUSDT钱包开发经验分享:omniUSDT【】Wallet development experience sharing: omniUSDT
3、【omni-USDT钱包开发】问我,问我社区,问我学院,专注软硬件开发,测试和运维平台技术文章分享问我,问我社区,问我学院,专注软硬件开发,测试和运维平台技术文章分享 http://wenwoha.com/3/course_article?act_id=21
4、【Bitcoin的一些在线工具】Bitcoin的一些在线工具_cylll123的博客-CSDN博客