UNPKG

@okxweb3/coin-bitcoin

Version:

@okxweb3/coin-bitcoin is a Bitcoin SDK for building Web3 wallets and applications. It supports BTC, BSV, DOGE, LTC, and TBTC, enabling private key management, transaction signing, address generation, and inscriptions like BRC-20, Runes, CAT, and Atomicals

248 lines 11.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.inscribeRefundFee = exports.createInscriptionTxCtxData = exports.InscriptionRefundFeeTool = void 0; const bitcoin = __importStar(require("./bitcoinjs-lib")); const crypto_lib_1 = require("@okxweb3/crypto-lib"); const taproot = __importStar(require("./taproot")); const bcrypto = __importStar(require("./bitcoinjs-lib/crypto")); const txBuild_1 = require("./txBuild"); const sigcost_1 = require("./sigcost"); const schnorr = crypto_lib_1.signUtil.schnorr.secp256k1.schnorr; const defaultTxVersion = 2; const defaultSequenceNum = 0xfffffffd; const defaultRevealOutValue = 546; const defaultMinChangeValue = 546; const maxStandardTxWeight = 4000000 / 10; class InscriptionRefundFeeTool { constructor() { this.network = bitcoin.networks.bitcoin; this.inscriptionTxCtxDataList = []; this.refundFeeTx = new bitcoin.Transaction(); this.commitTxPrevOutputFetcher = []; this.revealTxPrevOutputFetcher = []; this.mustTxFee = 0; this.dustChange = 0; this.commitAddrs = []; this.merkleRoot = new Uint8Array(); } static newInscriptionRefundFeeTool(network, request) { const tool = new InscriptionRefundFeeTool(); tool.network = network; const minChangeValue = request.minChangeValue || defaultMinChangeValue; if (request.inputs && request.inputs.length > 600) { throw new Error("invalid amount of inputs"); } let amountOfInputs = 100; if (request.maxAmountOfInput) { amountOfInputs = request.maxAmountOfInput; } if (amountOfInputs > request.inputs.length) { amountOfInputs = request.inputs.length; } const privateKey = request.inputs[0].privateKey; request.inscriptionRefundFeeDataList.forEach(inscriptionData => { tool.inscriptionTxCtxDataList.push(createInscriptionTxCtxData(network, inscriptionData, privateKey)); }); if (request.middleAddress && request.middleAddress != tool.inscriptionTxCtxDataList[0].commitTxAddress) { throw new Error("invalid middle address"); } else { tool.commitAddrs = [tool.inscriptionTxCtxDataList[0].commitTxAddress]; } tool.merkleRoot = tool.inscriptionTxCtxDataList[0].hash; const insufficient = tool.buildrRefundFeeTx(network, request.inputs.slice(0, amountOfInputs), request.changeAddress, 0, request.feeRate, minChangeValue); if (insufficient) { return tool; } tool.signRefundFeeTx(request.inputs.slice(0, amountOfInputs)); return tool; } buildrRefundFeeTx(network, prevOutputList, changeAddress, totalRevealPrevOutputValue, commitFeeRate, minChangeValue) { let totalSenderAmount = 0; const tx = new bitcoin.Transaction(); tx.version = defaultTxVersion; prevOutputList.forEach(prevOutput => { const hash = crypto_lib_1.base.reverseBuffer(crypto_lib_1.base.fromHex(prevOutput.txId)); tx.addInput(hash, prevOutput.vOut, defaultSequenceNum); this.commitTxPrevOutputFetcher.push(prevOutput.amount); totalSenderAmount += prevOutput.amount; }); const changePkScript = bitcoin.address.toOutputScript(changeAddress, network); tx.addOutput(changePkScript, 0); const txForEstimate = tx.clone(); signTx(txForEstimate, prevOutputList, this.network); const vsize = (0, sigcost_1.countAdjustedVsize)(txForEstimate, prevOutputList.map(a => a.address), network); const fee = Math.floor(vsize * commitFeeRate); const changeAmount = totalSenderAmount - totalRevealPrevOutputValue - fee; if (changeAmount >= minChangeValue) { tx.outs[tx.outs.length - 1].value = changeAmount; } else { if (tx.outs.length <= 1) { this.dustChange = changeAmount; return true; } tx.outs = tx.outs.slice(0, tx.outs.length - 1); txForEstimate.outs = txForEstimate.outs.slice(0, txForEstimate.outs.length - 1); const vsizeWithoutChange = (0, sigcost_1.countAdjustedVsize)(txForEstimate, prevOutputList.map(a => a.address), network); const feeWithoutChange = Math.floor(vsizeWithoutChange * commitFeeRate); if (totalSenderAmount - totalRevealPrevOutputValue - feeWithoutChange < 0) { this.mustTxFee = fee; return true; } } this.refundFeeTx = tx; return false; } signRefundFeeTx(inputs) { signTx(this.refundFeeTx, inputs, this.network, this.merkleRoot); } calculateFee() { let refundFeeTxFee = 0; this.refundFeeTx.ins.forEach((_, i) => { refundFeeTxFee += this.commitTxPrevOutputFetcher[i]; }); this.refundFeeTx.outs.forEach(out => { refundFeeTxFee -= out.value; }); return { refundFeeTxFee, }; } } exports.InscriptionRefundFeeTool = InscriptionRefundFeeTool; function signTx(tx, refundFeeTxPrevOutputList, network, merkleRoot = new Uint8Array()) { tx.ins.forEach((input, i) => { const addressType = (0, txBuild_1.getAddressType)(refundFeeTxPrevOutputList[i].address, network); const privateKey = crypto_lib_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(refundFeeTxPrevOutputList[i].privateKey, network)); const privateKeyHex = crypto_lib_1.base.toHex(privateKey); const publicKey = (0, txBuild_1.private2public)(privateKeyHex); if (addressType === 'segwit_taproot') { const prevOutScripts = refundFeeTxPrevOutputList.map(o => bitcoin.address.toOutputScript(o.address, network)); const values = refundFeeTxPrevOutputList.map(o => o.amount); const hash = tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT); const tweakedPrivKey = taproot.taprootTweakPrivKey(privateKey, merkleRoot); const signature = Buffer.from(schnorr.sign(hash, tweakedPrivKey, crypto_lib_1.base.randomBytes(32))); input.witness = [Buffer.from(signature)]; } else if (addressType === 'legacy') { const prevScript = bitcoin.address.toOutputScript(refundFeeTxPrevOutputList[i].address, network); const hash = tx.hashForSignature(i, prevScript, bitcoin.Transaction.SIGHASH_ALL); const signature = (0, txBuild_1.sign)(hash, privateKeyHex); const payment = bitcoin.payments.p2pkh({ signature: bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL), pubkey: publicKey, }); input.script = payment.input; } else { const pubKeyHash = bcrypto.hash160(publicKey); const prevOutScript = Buffer.of(0x19, 0x76, 0xa9, 0x14, ...pubKeyHash, 0x88, 0xac); const value = refundFeeTxPrevOutputList[i].amount; const hash = tx.hashForWitness(i, prevOutScript, value, bitcoin.Transaction.SIGHASH_ALL); const signature = (0, txBuild_1.sign)(hash, privateKeyHex); input.witness = [ bitcoin.script.signature.encode(signature, bitcoin.Transaction.SIGHASH_ALL), publicKey, ]; const redeemScript = Buffer.of(0x16, 0, 20, ...pubKeyHash); if (addressType === "segwit_nested") { input.script = redeemScript; } } }); } function createInscriptionTxCtxData(network, inscriptionData, privateKeyWif) { const privateKey = crypto_lib_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(privateKeyWif, network)); const internalPubKey = (0, txBuild_1.wif2Public)(privateKeyWif, network).slice(1); const ops = bitcoin.script.OPS; const inscriptionBuilder = []; inscriptionBuilder.push(internalPubKey); inscriptionBuilder.push(ops.OP_CHECKSIG); inscriptionBuilder.push(ops.OP_FALSE); inscriptionBuilder.push(ops.OP_IF); inscriptionBuilder.push(Buffer.from("ord")); inscriptionBuilder.push(ops.OP_DATA_1); inscriptionBuilder.push(ops.OP_DATA_1); inscriptionBuilder.push(Buffer.from(inscriptionData.contentType)); inscriptionBuilder.push(ops.OP_0); const maxChunkSize = 520; let body = Buffer.from(inscriptionData.body); let bodySize = body.length; for (let i = 0; i < bodySize; i += maxChunkSize) { let end = i + maxChunkSize; if (end > bodySize) { end = bodySize; } inscriptionBuilder.push(body.slice(i, end)); } inscriptionBuilder.push(ops.OP_ENDIF); const inscriptionScript = bitcoin.script.compile(inscriptionBuilder); const scriptTree = { output: inscriptionScript, }; const redeem = { output: inscriptionScript, redeemVersion: 0xc0, }; const { output, witness, hash, address } = bitcoin.payments.p2tr({ internalPubkey: internalPubKey, scriptTree, redeem, network, }); return { privateKey, inscriptionScript, commitTxAddress: address, commitTxAddressPkScript: output, witness: witness, hash: hash, revealTxPrevOutput: { pkScript: Buffer.alloc(0), value: 0, }, revealPkScript: bitcoin.address.toOutputScript(inscriptionData.revealAddr, network), }; } exports.createInscriptionTxCtxData = createInscriptionTxCtxData; function inscribeRefundFee(network, request) { const tool = InscriptionRefundFeeTool.newInscriptionRefundFeeTool(network, request); if (tool.mustTxFee > 0 || tool.dustChange != 0) { return { refundFeeTx: "", mustTxFee: tool.mustTxFee, dustChange: tool.dustChange }; } return { refundFeeTx: tool.refundFeeTx.toHex(), ...tool.calculateFee(), middleAddresses: tool.commitAddrs }; } exports.inscribeRefundFee = inscribeRefundFee; //# sourceMappingURL=inscribe_refund_fee.js.map