@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
559 lines • 23.9 kB
JavaScript
"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.buildRuneMainDeployData = exports.inscribe = exports.runesMainInscribe = exports.checkEtching = exports.MAX_SPACERS = exports.uint64Max = exports.uint128Max = exports.RunesMainInscriptionTool = 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 transaction_1 = require("./bitcoinjs-lib/transaction");
const txBuild_1 = require("./txBuild");
const bscript = __importStar(require("./bitcoinjs-lib/script"));
const sigcost_1 = require("./sigcost");
const runestones_1 = require("./runestones");
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 RunesMainInscriptionTool {
constructor() {
this.network = bitcoin.networks.bitcoin;
this.inscriptionTxCtxDataList = [];
this.revealTxs = [];
this.commitTx = new bitcoin.Transaction();
this.commitTxPrevOutputFetcher = [];
this.revealTxPrevOutputFetcher = [];
this.mustCommitTxFee = 0;
this.mustRevealTxFees = [];
this.commitAddrs = [];
}
static newInscriptionTool(network, request) {
const tool = new RunesMainInscriptionTool();
tool.network = network;
const revealOutValue = request.revealOutValue || defaultRevealOutValue;
const minChangeValue = request.minChangeValue || defaultMinChangeValue;
const useDefaultOutput = request.runeData.useDefaultOutput || false;
const defaultOutput = request.runeData.defaultOutput || 0;
const privateKey = request.commitTxPrevOutputList[0].privateKey;
if (!request.runeData.etching || !request.runeData.revealAddr) {
throw new Error("etching is null");
}
if (!checkEtching(request.runeData.etching)) {
throw new Error("invalid etching parameter");
}
let etching = request.runeData.etching;
let runeCommitment = (0, runestones_1.commitment)(etching.rune);
let runeOpReturnData = buildRuneMainDeployData(etching, useDefaultOutput, defaultOutput);
if (request.runeData.etching.contentType && request.runeData.etching.body) {
tool.inscriptionTxCtxDataList.push(createInscriptionTxCtxData(network, {
contentType: request.runeData.etching.contentType,
body: request.runeData.etching.body,
revealAddr: request.runeData.revealAddr
}, privateKey, runeCommitment));
}
else {
tool.inscriptionTxCtxDataList.push(createCommitmentScript(network, privateKey, request.runeData.revealAddr, runeCommitment));
}
const totalRevealPrevOutputValue = tool.buildEmptyRevealTx(network, revealOutValue, request.revealFeeRate, runeOpReturnData);
const insufficient = tool.buildCommitTx(network, request.commitTxPrevOutputList, request.changeAddress, totalRevealPrevOutputValue, request.commitFeeRate, minChangeValue);
if (insufficient) {
return tool;
}
tool.signCommitTx(request.commitTxPrevOutputList);
tool.completeRevealTx();
return tool;
}
buildEmptyRevealTx(network, revealOutValue, revealFeeRate, opReturnData) {
let totalPrevOutputValue = 0;
const revealTxs = [];
const mustRevealTxFees = [];
const commitAddrs = [];
this.inscriptionTxCtxDataList.forEach((inscriptionTxCtxData, i) => {
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
tx.addInput(Buffer.alloc(32), i, defaultSequenceNum);
if (opReturnData) {
tx.addOutput(opReturnData, 0);
}
tx.addOutput(inscriptionTxCtxData.revealPkScript, revealOutValue);
const emptySignature = Buffer.alloc(64);
const emptyControlBlockWitness = Buffer.alloc(33);
const txWitness = [];
txWitness.push(emptySignature);
txWitness.push(inscriptionTxCtxData.inscriptionScript);
txWitness.push(emptyControlBlockWitness);
const fee = Math.floor((tx.byteLength() + Math.floor(((0, transaction_1.vectorSize)(txWitness) + 2 + 3) / 4)) * revealFeeRate);
const prevOutputValue = revealOutValue + fee;
inscriptionTxCtxData.revealTxPrevOutput = {
pkScript: inscriptionTxCtxData.commitTxAddressPkScript,
value: prevOutputValue,
};
totalPrevOutputValue += prevOutputValue;
revealTxs.push(tx);
mustRevealTxFees.push(fee);
commitAddrs.push(inscriptionTxCtxData.commitTxAddress);
});
this.revealTxs = revealTxs;
this.mustRevealTxFees = mustRevealTxFees;
this.commitAddrs = commitAddrs;
return totalPrevOutputValue;
}
buildCommitTx(network, commitTxPrevOutputList, changeAddress, totalRevealPrevOutputValue, commitFeeRate, minChangeValue) {
let totalSenderAmount = 0;
const tx = new bitcoin.Transaction();
tx.version = defaultTxVersion;
commitTxPrevOutputList.forEach(commitTxPrevOutput => {
const hash = coin_base_1.base.reverseBuffer(coin_base_1.base.fromHex(commitTxPrevOutput.txId));
tx.addInput(hash, commitTxPrevOutput.vOut, defaultSequenceNum);
this.commitTxPrevOutputFetcher.push(commitTxPrevOutput.amount);
totalSenderAmount += commitTxPrevOutput.amount;
});
this.inscriptionTxCtxDataList.forEach(inscriptionTxCtxData => {
tx.addOutput(inscriptionTxCtxData.revealTxPrevOutput.pkScript, inscriptionTxCtxData.revealTxPrevOutput.value);
});
const changePkScript = bitcoin.address.toOutputScript(changeAddress, network);
tx.addOutput(changePkScript, 0);
const txForEstimate = tx.clone();
signTx(txForEstimate, commitTxPrevOutputList.map(i => ({ ...i, privateKey: '' })), this.network);
const vsize = (0, sigcost_1.countAdjustedVsize)(txForEstimate, commitTxPrevOutputList.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 {
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, commitTxPrevOutputList.map(a => a.address), network);
const feeWithoutChange = Math.floor(vsizeWithoutChange * commitFeeRate);
if (totalSenderAmount - totalRevealPrevOutputValue - feeWithoutChange < 0) {
this.mustCommitTxFee = fee;
return true;
}
}
this.commitTx = tx;
return false;
}
signCommitTx(commitTxPrevOutputList) {
signTx(this.commitTx, commitTxPrevOutputList, this.network);
}
completeRevealTx() {
this.revealTxs.forEach((revealTx, i) => {
revealTx.ins[0].hash = this.commitTx.getHash();
const prevOutScripts = [this.inscriptionTxCtxDataList[i].revealTxPrevOutput.pkScript];
const values = [this.inscriptionTxCtxDataList[i].revealTxPrevOutput.value];
this.revealTxPrevOutputFetcher.push(this.inscriptionTxCtxDataList[i].revealTxPrevOutput.value);
const hash = revealTx.hashForWitnessV1(0, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT, this.inscriptionTxCtxDataList[i].hash);
const signature = Buffer.from(schnorr.sign(hash, this.inscriptionTxCtxDataList[i].privateKey, coin_base_1.base.randomBytes(32)));
revealTx.ins[0].witness = [Buffer.from(signature), ...this.inscriptionTxCtxDataList[i].witness];
const revealWeight = revealTx.weight();
if (revealWeight > maxStandardTxWeight) {
throw new Error(`reveal(index ${i}) transaction weight greater than ${maxStandardTxWeight} (MAX_STANDARD_TX_WEIGHT): ${revealWeight}`);
}
});
}
calculateFee() {
let commitTxFee = 0;
this.commitTx.ins.forEach((_, i) => {
commitTxFee += this.commitTxPrevOutputFetcher[i];
});
this.commitTx.outs.forEach(out => {
commitTxFee -= out.value;
});
let revealTxFees = [];
this.revealTxs.forEach((revealTx, i) => {
let revealTxFee = 0;
revealTxFee += this.revealTxPrevOutputFetcher[i];
revealTxFee -= revealTx.outs[0].value;
revealTxFees.push(revealTxFee);
});
return {
commitTxFee,
revealTxFees,
};
}
}
exports.RunesMainInscriptionTool = RunesMainInscriptionTool;
exports.uint128Max = (BigInt(1) << BigInt(128)) - BigInt(1);
exports.uint64Max = (BigInt(1) << BigInt(64)) - BigInt(1);
const MAX_DIVISIBILITY = 38;
exports.MAX_SPACERS = 134217727;
function checkEtching(e) {
let premine = e.premine ? BigInt(e.premine) : BigInt(0);
if (premine > exports.uint128Max || premine < 0) {
return false;
}
if (e.terms) {
let amount = e.terms.amount ? BigInt(e.terms.amount) : BigInt(0);
let cap = e.terms.cap ? BigInt(e.terms.cap) : BigInt(0);
if (amount < 0 || amount > exports.uint128Max || cap < 0 || cap > exports.uint128Max) {
return false;
}
if (amount * cap > exports.uint128Max) {
return false;
}
let supply = premine + amount * cap;
if (supply > exports.uint128Max) {
return false;
}
if (e.terms.height) {
if (e.terms.height.start) {
if (e.terms.height.start < 0 || e.terms.height.start > exports.uint64Max) {
return false;
}
}
if (e.terms.height.end) {
if (e.terms.height.end < 0 || e.terms.height.end > exports.uint64Max) {
return false;
}
}
}
}
if (e.divisibility) {
if (e.divisibility < 0 || e.divisibility > MAX_DIVISIBILITY) {
return false;
}
}
if (typeof e.rune.value === "string") {
let spacers = (0, runestones_1.getSpacersVal)(e.rune.value);
if (spacers > exports.MAX_SPACERS) {
return false;
}
let v = (0, runestones_1.base26Encode)((0, runestones_1.removeSpacers)(e.rune.value));
if (v > exports.uint128Max) {
return false;
}
}
return true;
}
exports.checkEtching = checkEtching;
function runesMainInscribe(network, request) {
const tool = RunesMainInscriptionTool.newInscriptionTool(network, request);
if (tool.mustCommitTxFee > 0) {
return {
commitTx: "",
revealTxs: [],
commitTxFee: tool.mustCommitTxFee,
revealTxFees: tool.mustRevealTxFees,
commitAddrs: tool.commitAddrs,
};
}
return {
commitTx: tool.commitTx.toHex(),
revealTxs: tool.revealTxs.map(revealTx => revealTx.toHex()),
...tool.calculateFee(),
commitAddrs: tool.commitAddrs,
};
}
exports.runesMainInscribe = runesMainInscribe;
function signTx(tx, commitTxPrevOutputList, network) {
tx.ins.forEach((input, i) => {
const addressType = (0, txBuild_1.getAddressType)(commitTxPrevOutputList[i].address, network);
if (commitTxPrevOutputList[i].privateKey == '') {
const { witness, script } = (0, txBuild_1.fakeSign)(addressType);
if (witness !== undefined) {
input.witness = witness;
}
if (script !== undefined) {
input.script = script;
}
return;
}
const privateKey = coin_base_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(commitTxPrevOutputList[i].privateKey, network));
const privateKeyHex = coin_base_1.base.toHex(privateKey);
const publicKey = (0, txBuild_1.private2public)(privateKeyHex);
if (addressType === 'segwit_taproot') {
const prevOutScripts = commitTxPrevOutputList.map(o => bitcoin.address.toOutputScript(o.address, network));
const values = commitTxPrevOutputList.map(o => o.amount);
const hash = tx.hashForWitnessV1(i, prevOutScripts, values, bitcoin.Transaction.SIGHASH_DEFAULT);
const tweakedPrivKey = taproot.taprootTweakPrivKey(privateKey);
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(commitTxPrevOutputList[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 = commitTxPrevOutputList[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 createCommitmentScript(network, privateKeyWif, revealAddr, commitment) {
const privateKey = coin_base_1.base.fromHex((0, txBuild_1.privateKeyFromWIF)(privateKeyWif, network));
const internalPubKey = (0, txBuild_1.wif2Public)(privateKeyWif, network).slice(1);
const inscriptionBuilder = [];
inscriptionBuilder.push(internalPubKey);
const ops = bitcoin.script.OPS;
inscriptionBuilder.push(ops.OP_CHECKSIG);
inscriptionBuilder.push(ops.OP_FALSE);
inscriptionBuilder.push(ops.OP_IF);
inscriptionBuilder.push(commitment);
inscriptionBuilder.push(ops.OP_ENDIF);
let 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(revealAddr, network),
};
}
function createInscriptionTxCtxData(network, inscriptionData, privateKeyWif, inscriptionAddData) {
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);
if (inscriptionAddData && inscriptionAddData.length > 0) {
inscriptionBuilder.push(inscriptionAddData);
inscriptionBuilder.push(ops.OP_DROP);
}
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),
};
}
function inscribe(network, request) {
const tool = RunesMainInscriptionTool.newInscriptionTool(network, request);
if (tool.mustCommitTxFee > 0) {
return {
commitTx: "",
revealTxs: [],
commitTxFee: tool.mustCommitTxFee,
revealTxFees: tool.mustRevealTxFees,
commitAddrs: tool.commitAddrs,
};
}
return {
commitTx: tool.commitTx.toHex(),
revealTxs: tool.revealTxs.map(revealTx => revealTx.toHex()),
...tool.calculateFee(),
commitAddrs: tool.commitAddrs,
};
}
exports.inscribe = inscribe;
function buildRuneMainDeployData(etching, useDefaultOutput, defaultOutput) {
let msg = buildMessage(etching, useDefaultOutput, defaultOutput);
let msgBuff = toBuffer(msg);
const prefix = Buffer.from('6a5d', 'hex');
let pushNum;
if (msgBuff.length < 0x4c) {
pushNum = Buffer.alloc(1);
pushNum.writeUint8(msgBuff.length);
}
else if (msgBuff.length < 0x100) {
pushNum = Buffer.alloc(2);
pushNum.writeUint8(0x4c);
pushNum.writeUint8(msgBuff.length, 1);
}
else if (msgBuff.length < 0x10000) {
pushNum = Buffer.alloc(3);
pushNum.writeUint8(0x4d);
pushNum.writeUint16LE(msgBuff.length, 1);
}
else if (msgBuff.length < 0x100000000) {
pushNum = Buffer.alloc(5);
pushNum.writeUint8(0x4e);
pushNum.writeUint32LE(msgBuff.length, 1);
}
else {
throw new Error("runestone too big!");
}
return bscript.compile(Buffer.concat([prefix, pushNum, msgBuff]));
}
exports.buildRuneMainDeployData = buildRuneMainDeployData;
function toBuffer(msg) {
let buffArr = [];
for (const [tag, vals] of msg) {
for (const val of vals) {
const tagBuff = Buffer.alloc(1);
tagBuff.writeUInt8(tag);
buffArr.push(tagBuff);
buffArr.push(Buffer.from((0, runestones_1.encodeLEB128)(val)));
}
}
return Buffer.concat(buffArr);
}
function buildMessage(etching, useDefaultOutput, defaultOutput) {
let fields = new Map();
let flags = 1;
if (etching.terms) {
let mask = 1 << runestones_1.Flag.Terms;
flags |= mask;
}
if (etching.turbo) {
let mask = 1 << runestones_1.Flag.Turbo;
flags |= mask;
}
fields.set(runestones_1.Tag.Flags, [BigInt(flags)]);
let runeValue;
if (typeof etching.rune.value === 'string') {
runeValue = (0, runestones_1.base26Encode)((0, runestones_1.removeSpacers)(etching.rune.value));
}
else {
runeValue = etching.rune.value;
}
fields.set(runestones_1.Tag.Rune, [BigInt(runeValue)]);
if (etching.divisibility) {
fields.set(runestones_1.Tag.Divisibility, [BigInt(etching.divisibility)]);
}
if (etching.spacers) {
fields.set(runestones_1.Tag.Spacers, [BigInt(etching.spacers)]);
}
else {
let spacers;
if (typeof etching.rune.value === "bigint") {
spacers = 0;
}
else {
spacers = (0, runestones_1.getSpacersVal)(etching.rune.value);
}
if (spacers !== 0) {
fields.set(runestones_1.Tag.Spacers, [BigInt(spacers)]);
}
}
if (etching.symbol) {
fields.set(runestones_1.Tag.Symbol, [BigInt(etching.symbol.charCodeAt(0))]);
}
if (etching.premine) {
fields.set(runestones_1.Tag.Premine, [BigInt(etching.premine)]);
}
if (etching.terms) {
if (etching.terms.amount) {
fields.set(runestones_1.Tag.Amount, [BigInt(etching.terms.amount)]);
}
if (etching.terms.cap) {
fields.set(runestones_1.Tag.Cap, [BigInt(etching.terms.cap)]);
}
if (etching.terms.height) {
const heightStart = etching.terms.height.start;
if (heightStart) {
fields.set(runestones_1.Tag.HeightStart, [BigInt(heightStart)]);
}
const heightEnd = etching.terms.height.end;
if (heightEnd) {
fields.set(runestones_1.Tag.HeightEnd, [BigInt(heightEnd)]);
}
}
if (etching.terms.offset) {
const offsetStart = etching.terms.offset.start;
if (offsetStart) {
fields.set(runestones_1.Tag.OffsetStart, [BigInt(offsetStart)]);
}
const offsetEnd = etching.terms.offset.end;
if (offsetEnd) {
fields.set(runestones_1.Tag.OffsetEnd, [BigInt(offsetEnd)]);
}
}
}
if (useDefaultOutput && defaultOutput) {
fields.set(runestones_1.Tag.Pointer, [BigInt(defaultOutput)]);
}
return fields;
}
//# sourceMappingURL=runesMain.js.map