@okxweb3/coin-bitcoin
Version:
@ok/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.
256 lines • 11.4 kB
JavaScript
;
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 coin_base_1 = require("@okxweb3/coin-base");
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 = coin_base_1.base.reverseBuffer(coin_base_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 = coin_base_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(refundFeeTxPrevOutputList[i].privateKey, network));
const privateKeyHex = coin_base_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, coin_base_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 = coin_base_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