@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.
549 lines • 23.6 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.RuneMainTestWallet = exports.RuneMainWallet = exports.ErrCodeMultipleRuneId = exports.ErrCodeRuneIdNotStandard = exports.ErrCodeOpreturnExceeds = exports.ErrCodeLessRunesMainAmt = void 0;
const coin_base_1 = require("@okxweb3/coin-base");
const BtcWallet_1 = require("./BtcWallet");
const bitcoin = __importStar(require("../index"));
const index_1 = require("../index");
const rune_1 = require("../rune");
const coin_base_2 = require("@okxweb3/coin-base");
const runesMain_1 = require("../runesMain");
exports.ErrCodeLessRunesMainAmt = 2010300;
exports.ErrCodeOpreturnExceeds = 2010301;
exports.ErrCodeRuneIdNotStandard = 2010302;
exports.ErrCodeMultipleRuneId = 2010303;
class RuneMainWallet extends BtcWallet_1.BtcWallet {
convert2RuneTx(paramData) {
const clonedParamData = (0, coin_base_1.cloneObject)(paramData);
if (clonedParamData.runeData.useDefaultOutput == true &&
clonedParamData.runeData.defaultOutput == undefined) {
clonedParamData.runeData.defaultOutput = 0;
}
if (clonedParamData.runeData.mint == true &&
clonedParamData.runeData.mintNum == undefined) {
clonedParamData.runeData.mintNUm = 1;
}
for (let input of clonedParamData.inputs) {
let dataArray = input.data;
if (dataArray != null && dataArray instanceof Array) {
for (let data of dataArray) {
if (typeof data['amount'] === 'string') {
if (clonedParamData.runeData.mint) {
data['amount'] = BigInt(1);
}
else {
data['amount'] = BigInt(data['amount']);
}
}
}
}
}
for (let output of clonedParamData.outputs) {
let dataArray = output.data;
if (dataArray != null && dataArray instanceof Array) {
for (let data of dataArray) {
if (typeof data['amount'] === 'string') {
if (clonedParamData.runeData.mint) {
data['amount'] = BigInt(1);
}
else {
data['amount'] = BigInt(data['amount']);
}
}
}
}
}
let inputs = clonedParamData.inputs;
const runeInputMap = new Map();
for (const input of inputs) {
let dataArray = input.data;
if (dataArray != null && dataArray instanceof Array) {
for (const data of dataArray) {
let runeId = data['id'];
let runeAmount = BigInt(1);
if (!clonedParamData.runeData.mint) {
runeAmount = BigInt(data['amount']);
}
if (runeId == null || runeAmount == null) {
continue;
}
if (runeId.split(':').length <= 1) {
throw new Error(JSON.stringify({
errCode: exports.ErrCodeRuneIdNotStandard,
date: { runeId: runeId },
}));
}
let beforeAmount = runeInputMap.get(runeId);
if (beforeAmount == null) {
runeInputMap.set(runeId, runeAmount);
}
else {
runeInputMap.set(runeId, BigInt(beforeAmount) + BigInt(runeAmount));
}
}
}
}
if (runeInputMap.size > 1) {
throw new Error(JSON.stringify({
errCode: exports.ErrCodeMultipleRuneId,
date: { runeInputMapSize: runeInputMap.size },
}));
}
let outputs = clonedParamData.outputs;
const runeSendMap = new Map();
for (const output of outputs) {
let data = output.data;
if (data != null) {
let runeId = data['id'];
let runeAmount = BigInt(1);
if (!clonedParamData.runeData.mint) {
runeAmount = BigInt(data['amount']);
}
if (runeId == null || runeAmount == null) {
continue;
}
let beforeAmount = runeSendMap.get(runeId);
if (beforeAmount == null) {
runeSendMap.set(runeId, runeAmount);
}
else {
runeSendMap.set(runeId, BigInt(beforeAmount) + BigInt(runeAmount));
}
}
}
let isRuneChange = false;
for (const id of runeInputMap.keys()) {
let inputAmount = runeInputMap.get(id);
let sendAmount = runeSendMap.get(id);
if ((inputAmount != null &&
sendAmount != null &&
inputAmount > sendAmount) ||
(inputAmount != null && sendAmount == null)) {
if (clonedParamData.runeData.useDefaultOutput == false) {
isRuneChange = true;
}
}
}
let outputIndex = 0;
let updateOutputs = [];
if (isRuneChange) {
let runeChange = {
address: clonedParamData.address,
amount: 546,
};
updateOutputs.push(runeChange);
outputIndex++;
}
const typedEdicts = [];
for (const output of outputs) {
let data = output.data;
if (data != null) {
let runeId = data['id'];
let runeAmount = BigInt(1);
if (!clonedParamData.runeData.mint) {
runeAmount = BigInt(data['amount']);
}
if (runeId == null || runeAmount == null) {
continue;
}
const typedEdict = {
block: parseInt(runeId.split(':')[0]),
id: parseInt(runeId.split(':')[1]),
amount: BigInt(runeAmount),
output: outputIndex,
};
typedEdicts.push(typedEdict);
}
output.data = null;
updateOutputs.push(output);
outputIndex++;
}
if (clonedParamData.runeData.useDefaultOutput == true &&
clonedParamData.runeData.defaultOutput > updateOutputs.length - 1) {
throw new Error(JSON.stringify({
errCode: exports.ErrCodeLessRunesMainAmt,
date: {
defaultOutput: clonedParamData.runeData.defaultOutput,
ouputLenth: updateOutputs.length,
},
}));
}
return {
inputs: clonedParamData.inputs,
outputs: updateOutputs,
address: clonedParamData.address,
feePerB: clonedParamData.feePerB,
runeData: {
edicts: typedEdicts,
etching: clonedParamData.runeData.etching,
burn: clonedParamData.runeData.burn,
defaultOutput: clonedParamData.runeData.defaultOutput,
mint: clonedParamData.runeData.mint,
mintNum: clonedParamData.runeData.mintNum,
},
};
}
getMockMinRuneTx(paramData, curRuneInfo) {
const clonedParamData = (0, coin_base_1.cloneObject)(paramData);
const typedEdicts = [];
const typedEdict = {
block: parseInt(curRuneInfo.id.split(':')[0]),
id: parseInt(curRuneInfo.id.split(':')[1]),
amount: BigInt(1),
output: 0,
};
typedEdicts.push(typedEdict);
return {
inputs: [
{
txId: clonedParamData.inputs[0].txId,
vOut: 0,
amount: 600,
data: [curRuneInfo],
},
],
outputs: [
{
address: clonedParamData.address,
amount: 546,
data: curRuneInfo,
},
],
address: clonedParamData.address,
feePerB: clonedParamData.feePerB,
runeData: {
edicts: typedEdicts,
etching: clonedParamData.runeData.etching,
burn: clonedParamData.runeData.burn,
defaultOutput: clonedParamData.runeData.defaultOutput,
mint: clonedParamData.runeData.mint,
mintNum: clonedParamData.runeData.mintNum,
},
};
}
getMinRuneTx(paramData, curRuneInfo, curInput, curOutput) {
const clonedParamData = (0, coin_base_1.cloneObject)(paramData);
return {
inputs: [curInput],
outputs: [curOutput],
address: clonedParamData.address,
feePerB: clonedParamData.feePerB,
RuneData: clonedParamData.runeData,
};
}
async signTransaction(param) {
try {
const network = this.network();
let txHex = null;
if (param.data.runeData.etching) {
return (0, runesMain_1.runesMainInscribe)(network, param.data);
}
else if (param.data.runeData.mintNum == undefined ||
param.data.runeData.mintNum <= 1) {
const privateKey = param.privateKey;
if (!param.data.runeData) {
return Promise.reject('missing runeData');
}
const runeTx = this.convert2RuneTx(param.data);
const opReturnOutput = this.getRuneMainOpReturnOutput(network, runeTx.runeData);
runeTx.outputs.push(opReturnOutput);
txHex = (0, index_1.signBtc)(runeTx, privateKey, network);
return Promise.resolve(txHex);
}
else if (param.data.runeData.mint &&
!param.data.runeData.serialMint) {
let txHexs = [];
const privateKey = param.privateKey;
if (!param.data.runeData) {
return Promise.reject('missing runeData');
}
let mintData = {
id: param.data.outputs[0].data.id,
amount: param.data.outputs[0].data.amount,
mintNum: param.data.runeData.mintNum,
};
let baseMintTx = this.getMockMinRuneTx(param.data, mintData);
const opMintReturnOutput = this.getRuneMainOpReturnOutput(network, baseMintTx.runeData);
baseMintTx.outputs.push(opMintReturnOutput);
txHex = (0, index_1.signBtc)(baseMintTx, privateKey, network);
const baseMintfee = bitcoin.estimateBtcFee(baseMintTx, this.network()) + 546;
const runeTx = this.convert2RuneTx(param.data);
let batchMintStatNum = runeTx.outputs.length;
for (let i = 0; i < mintData.mintNum - 1; i++) {
runeTx.outputs.push({
address: param.data.address,
amount: baseMintfee,
});
}
const opReturnOutput = this.getRuneMainOpReturnOutput(network, runeTx.runeData);
runeTx.outputs.push(opReturnOutput);
txHex = (0, index_1.signBtc)(runeTx, privateKey, network);
const parentTxId = index_1.Transaction.fromHex(txHex).getId();
txHexs.push(txHex);
for (let i = 0; i < mintData.mintNum - 1; i++) {
let curInput = {
txId: parentTxId,
vOut: batchMintStatNum,
address: param.data.address,
amount: baseMintfee,
data: [mintData],
};
let curOutput = {
address: param.data.address,
amount: 546,
};
batchMintStatNum += 1;
let curSubTx = this.getMinRuneTx(param.data, mintData, curInput, curOutput);
curSubTx.outputs.push(opReturnOutput);
let curSubTxHex = (0, index_1.signBtc)(curSubTx, privateKey, network);
txHexs.push(curSubTxHex);
}
return Promise.resolve(txHexs);
}
else {
let txHexs = [];
const privateKey = param.privateKey;
if (!param.data.runeData) {
return Promise.reject('missing runeData');
}
let mintData = {
id: param.data.outputs[0].data.id,
amount: 1,
mintNum: param.data.runeData.mintNum,
};
let baseMintTx = this.getMockMinRuneTx(param.data, mintData);
const opMintReturnOutput = (0, rune_1.buildRuneMainMintOp)(mintData.id, false, 0, true);
baseMintTx.outputs.push(opMintReturnOutput);
const baseMintfee = bitcoin.estimateBtcFee(baseMintTx, this.network());
let curAmount = (mintData.mintNum - 1) * baseMintfee + 546;
const runeTx = this.convert2RuneTxSerialMint(param.data, curAmount);
runeTx.outputs.push(opMintReturnOutput);
txHex = (0, index_1.signBtc)(runeTx, privateKey, network);
let parentTxId = index_1.Transaction.fromHex(txHex).getId();
txHexs.push(txHex);
for (let i = 1; i < mintData.mintNum; i++) {
let curInput = {
txId: parentTxId,
vOut: 0,
address: param.data.address,
amount: curAmount,
};
curAmount = curAmount - baseMintfee;
let curOutput = {
address: param.data.address,
amount: curAmount,
};
let curSubTx = this.getMinRuneTx(param.data, mintData, curInput, curOutput);
curSubTx.outputs.push(opMintReturnOutput);
let curSubTxHex = (0, index_1.signBtc)(curSubTx, privateKey, network);
parentTxId = index_1.Transaction.fromHex(curSubTxHex).getId();
txHexs.push(curSubTxHex);
}
return Promise.resolve(txHexs);
}
}
catch (e) {
return Promise.reject(coin_base_1.SignTxError);
}
}
getRuneMainOpReturnOutput(network, runeData) {
let isMainnet = false;
if (index_1.networks.bitcoin === network) {
isMainnet = true;
}
if (runeData.useDefaultOutput == undefined) {
runeData.useDefaultOutput = false;
}
if (runeData.defaultOutput == undefined) {
runeData.defaultOutput = 0;
}
if (runeData.mint == undefined) {
runeData.mint = false;
}
if (runeData.mintNum == undefined) {
runeData.mintNum = 0;
}
const opReturnScript = (0, rune_1.buildRuneMainMintData)(isMainnet, runeData.edicts, runeData.useDefaultOutput, runeData.defaultOutput, runeData.mint, runeData.mintNum);
return {
address: '',
amount: 0,
omniScript: coin_base_2.base.toHex(opReturnScript),
};
}
async estimateFee(param) {
try {
const network = this.network();
if (!param.data.runeData) {
return Promise.reject('missing runeData');
}
const runeTx = this.convert2RuneTx(param.data);
const opReturnOutput = this.getRuneMainOpReturnOutput(this.network(), runeTx.runeData);
runeTx.outputs.push(opReturnOutput);
let mintData = {
id: param.data.outputs[0].data.id,
amount: param.data.outputs[0].data.amount,
mintNum: param.data.runeData.mintNum,
};
for (let i = 0; i < mintData.mintNum - 1; i++) {
runeTx.outputs.push({
address: param.data.address,
amount: 8000,
});
}
const fee = bitcoin.estimateBtcFee(runeTx, this.network());
if (param.data.runeData.mintNum == undefined ||
param.data.runeData.mintNum <= 1) {
return Promise.resolve(fee);
}
else if (param.data.runeData.mint &&
!param.data.runeData.serialMint) {
let fees = [];
fees.push(fee);
let mintData = {
id: param.data.outputs[0].data.id,
amount: param.data.outputs[0].data.amount,
mintNum: param.data.runeData.mintNum,
};
let baseMintTx = this.getMockMinRuneTx(param.data, mintData);
const opMintReturnOutput = this.getRuneMainOpReturnOutput(this.network(), baseMintTx.runeData);
baseMintTx.outputs.push(opMintReturnOutput);
const baseMintfee = bitcoin.estimateBtcFee(baseMintTx, this.network());
for (let i = 0; i < param.data.runeData.mintNum - 1; i++) {
fees.push(baseMintfee);
}
return Promise.resolve(fees);
}
else if (param.data.runeData.mint &&
param.data.runeData.serialMint) {
let fees = [];
if (!param.data.runeData) {
return Promise.reject('missing runeData');
}
let mintData = {
id: param.data.outputs[0].data.id,
amount: 1,
mintNum: param.data.runeData.mintNum,
};
let baseMintTx = this.getMockMinRuneTx(param.data, mintData);
const opMintReturnOutput = (0, rune_1.buildRuneMainMintOp)(mintData.id, false, 0, true);
baseMintTx.outputs.push(opMintReturnOutput);
const baseMintfee = bitcoin.estimateBtcFee(baseMintTx, this.network());
let curAmount = (mintData.mintNum - 1) * baseMintfee + 546;
const runeTx = this.convert2RuneTxSerialMint(param.data, curAmount);
runeTx.outputs.push(opMintReturnOutput);
let fee = bitcoin.estimateBtcFee(runeTx, network);
fees.push(fee);
for (let i = 1; i < mintData.mintNum; i++) {
fees.push(baseMintfee);
}
return Promise.resolve(fees);
}
}
catch (e) {
return Promise.reject(coin_base_1.EstimateFeeError);
}
}
convert2RuneTxSerialMint(paramData, outputAmount) {
const clonedParamData = (0, coin_base_1.cloneObject)(paramData);
let curOutputs = [
{
address: clonedParamData.address,
amount: outputAmount,
},
];
return {
inputs: clonedParamData.inputs,
outputs: curOutputs,
address: clonedParamData.address,
feePerB: clonedParamData.feePerB,
runeData: {
edicts: clonedParamData.runeData.edicts,
etching: clonedParamData.runeData.etching,
burn: clonedParamData.runeData.burn,
defaultOutput: clonedParamData.runeData.defaultOutput,
mint: clonedParamData.runeData.mint,
mintNum: clonedParamData.runeData.mintNum,
},
};
}
convert2RuneTxPsbt(paramData) {
const clonedParamData = (0, coin_base_1.cloneObject)(paramData);
return {
inputs: clonedParamData.inputs,
outputs: clonedParamData.outputs,
address: clonedParamData.address,
feePerB: clonedParamData.feePerB,
runeData: {
edicts: clonedParamData.runeData.edicts,
etching: clonedParamData.runeData.etching,
burn: clonedParamData.runeData.burn,
defaultOutput: clonedParamData.runeData.defaultOutput,
mint: clonedParamData.runeData.mint,
mintNum: clonedParamData.runeData.mintNum,
},
};
}
async buildPsbt(param) {
const network = this.network();
let txHex = null;
try {
if (!param.data.runeData) {
return Promise.reject('missing runeData');
}
const runeTx = this.convert2RuneTxPsbt(param.data);
const opReturnOutput = this.getRuneMainOpReturnOutput(network, runeTx.runeData);
runeTx.outputs.push(opReturnOutput);
let fakeAddr = 'KwdkfXMV2wxDVDMPPuFZsio3NeCskAUd4N2U4PriTgpj2MqAGmmc';
if (runeTx.dustSize == undefined) {
runeTx.dustSize = 546;
}
let { inputAmount, outputAmount, virtualSize } = (0, index_1.calculateTxSize)(runeTx.inputs, runeTx.outputs, runeTx.address, fakeAddr, network, runeTx.dustSize);
let changeAmount = inputAmount -
outputAmount -
Math.ceil(virtualSize * runeTx.feePerB);
if (changeAmount > runeTx.dustSize) {
runeTx.outputs.push({
address: runeTx.address,
amount: changeAmount,
});
}
const txHex = (0, index_1.buildPsbt)(runeTx, network);
const res = [txHex, changeAmount];
return Promise.resolve(res);
}
catch (e) {
return Promise.reject(e);
}
}
}
exports.RuneMainWallet = RuneMainWallet;
class RuneMainTestWallet extends RuneMainWallet {
network() {
return bitcoin.networks.testnet;
}
}
exports.RuneMainTestWallet = RuneMainTestWallet;
//# sourceMappingURL=RuneMainWallet.js.map