【重磅推荐】基于【bitcoinjs-lib】对比【bitcore-lib】两个库开发Bitcoin-Wallet的实战总结

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";

/**
 * 参考外国人写的案例【基于bitpaybitcore-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) {
        /**
         * 功能:重写父类方法【基于bitpaybitcore-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博客 

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...
win11本地账户怎么改名?win11很多操作都变了样,用户如果想要...