基于【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 {};
}
}
参考:
【重磅推荐】基于【bitcoinjs-lib】对比【bitcore-lib】两个库开发Bitcoin-Wallet的实战总结_比特币爱好者007的博客-CSDN博客