nerve-sdk-js
Version:
nerve nerve-js nerve-sdk nerve-js-sdk
747 lines (710 loc) • 35.1 kB
JavaScript
const {ECPairFactory} = require('ecpair');
const bufferutils_1 = require('bitcoinjs-lib/src/bufferutils');
const bitcoin = require('bitcoinjs-lib');
const bitcore = require('bitcore-lib');
const http = require("../test/api/https");
const nerve = require("../index");
let ECPair
const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33));
const MAX_SAFE_INTEGER = 9007199254740991;
function estimateTxSizeHeader(mainnet) {
if (!mainnet) mainnet = false;
const currentNetwork = mainnet ? bitcoin.networks.bitcoin : bitcoin.networks.testnet;
const keyPair = ECPair.fromWIF(
'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr',
);
const script0 = bitcoin.payments.p2pkh({network: currentNetwork, pubkey: keyPair.publicKey});
const script1 = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({network: currentNetwork, pubkey: keyPair.publicKey}),
});
const script2 = bitcoin.payments.p2wpkh({network: currentNetwork, pubkey: keyPair.publicKey});
const childNodeXOnlyPubkey = toXOnly(keyPair.publicKey);
const script3 = bitcoin.payments.p2tr({
network: currentNetwork,
internalPubkey: childNodeXOnlyPubkey,
});
return {currentNetwork, keyPair, script0, script1, script2, script3, childNodeXOnlyPubkey};
}
function estimateTxSizeBody(currentNetwork, inputCount, inputData, address0, address1, address2, address3, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray) {
const psbt = new bitcoin.Psbt({network: currentNetwork});
psbt.addInput(inputData);
for (let i = 1; i < inputCount; i++) {
const _inputData = Object.assign({}, inputData);
_inputData.index = i;
psbt.addInput(_inputData);
}
for (let i = 0; i < opReturnArray.length; i++) {
let opReturnBuffer = opReturnArray[i];
psbt.addOutput({
script: bitcoin.payments.embed({data: [opReturnBuffer]}).output,
value: 0,
})
}
for (let i = 0; i < legacyOutputCount; i++) {
psbt.addOutput({
address: address0,
value: 1002000,
})
}
for (let i = 0; i < nestedSegwitOutputCount; i++) {
psbt.addOutput({
address: address1,
value: 1002000,
})
}
for (let i = 0; i < nativeSegwitOutputCount; i++) {
psbt.addOutput({
address: address2,
value: 1002000,
})
}
for (let i = 0; i < taprootOutputCount; i++) {
psbt.addOutput({
address: address3,
value: 1002000,
})
}
return psbt;
}
function estimateTxSizeFooter(psbt) {
psbt.finalizeAllInputs();
let tx = psbt.extractTransaction(true);
let vSize = tx.virtualSize();
return vSize + 1;
}
function estimateLegacyTxSize(mainnet, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray) {
try {
const {
currentNetwork,
keyPair,
script0,
script1,
script2,
script3,
childNodeXOnlyPubkey
} = estimateTxSizeHeader(mainnet);
const inputData = {
hash: "96cd2cef275b9de5dba26ac969224689eabe71b0ac60787510a10e16af24b7a7",
index: 0,
nonWitnessUtxo: Buffer.from('010000000190a1ae9724b99311109e72d76684a7db5c181b9aad4d37c0698b7e1846ce5a14010000006a47304402201d54971b87675a4f874b1565153cd2b4afea1045292a5024185d68b989931fba02203fe1aa43c40cc88a74e65780c2f37403f113d4ca7df38d3c9e2f99ffbab067a501210365db9da3f8a260078a7e8f8b708a1161468fb2323ffda5ec16b261ec1056f455ffffffff65f0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88acf0e62607000000001976a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac00000000', 'hex')
};
const psbt = estimateTxSizeBody(currentNetwork, inputCount, inputData, script0.address, script1.address, script2.address, script3.address, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
for (let i = 0; i < inputCount; i++) {
psbt.signInput(i, keyPair);
}
return estimateTxSizeFooter(psbt);
} catch (e) {
console.log(e);
throw e;
}
}
function estimateNestedSegwitTxSize(mainnet, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray) {
try {
const {
currentNetwork,
keyPair,
script0,
script1,
script2,
script3,
childNodeXOnlyPubkey
} = estimateTxSizeHeader(mainnet);
const inputData = {
hash: "c904f6daae3617a2611e30bfbb782a8ad4845fb25e1a6759ed9eee25e2990ad3",
index: 0,
witnessUtxo: {script: script1.output, value: 11181008},
redeemScript: script1.redeem.output
};
const psbt = estimateTxSizeBody(currentNetwork, inputCount, inputData, script0.address, script1.address, script2.address, script3.address, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
for (let i = 0; i < inputCount; i++) {
psbt.signInput(i, keyPair);
}
return estimateTxSizeFooter(psbt);
} catch (e) {
console.log(e);
throw e;
}
}
function estimateNativeSegwitTxSize(mainnet, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray) {
try {
const {
currentNetwork,
keyPair,
script0,
script1,
script2,
script3,
childNodeXOnlyPubkey
} = estimateTxSizeHeader(mainnet);
const inputData = {
hash: "c904f6daae3617a2611e30bfbb782a8ad4845fb25e1a6759ed9eee25e2990ad3",
index: 0,
witnessUtxo: {script: script2.output, value: 11181008}
};
const psbt = estimateTxSizeBody(currentNetwork, inputCount, inputData, script0.address, script1.address, script2.address, script3.address, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
for (let i = 0; i < inputCount; i++) {
psbt.signInput(i, keyPair);
}
return estimateTxSizeFooter(psbt);
} catch (e) {
console.log(e);
throw e;
}
}
function estimateTaprootTxSize(mainnet, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray) {
try {
const {
currentNetwork,
keyPair,
script0,
script1,
script2,
script3,
childNodeXOnlyPubkey
} = estimateTxSizeHeader(mainnet);
const tweakedChildNode = keyPair.tweak(
bitcoin.crypto.taggedHash('TapTweak', childNodeXOnlyPubkey),
);
const inputData = {
hash: "a5c83bbcdd14bc1b502e4a9e349409dbfbe1afa6eb94decea942b92dc1e2231c",
index: 0,
witnessUtxo: {script: script3.output, value: 11181008},
tapInternalKey: childNodeXOnlyPubkey
};
const psbt = estimateTxSizeBody(currentNetwork, inputCount, inputData, script0.address, script1.address, script2.address, script3.address, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
for (let i = 0; i < inputCount; i++) {
psbt.signInput(i, tweakedChildNode);
}
return estimateTxSizeFooter(psbt);
} catch (e) {
console.log(e);
throw e;
}
}
function getSpendingUtxos(utxos, spending) {
let spendingUtxos = [];
let total = 0;
let inputCount = 0;
for (let i = 0; i < utxos.length; i++) {
let utxo = utxos[i];
if (total >= spending) {
break;
}
inputCount++;
total += utxo.amount;
spendingUtxos.push(utxo);
}
if (total < spending) {
throw "not enough utxo";
}
return {spendingUtxos, total, inputCount};
}
function createSpendingUtxosAndOutput(mainnet, txType, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray) {
const currentNetwork = mainnet ? bitcoin.networks.bitcoin : bitcoin.networks.testnet;
const pubkeyBuffer = Buffer.from(pubkeyHex, 'hex');
let script;
if (txType == 0) {
script = bitcoin.payments.p2pkh({network: currentNetwork, pubkey: pubkeyBuffer});
} else if (txType == 1) {
script = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({network: currentNetwork, pubkey: pubkeyBuffer}),
});
} else if (txType == 2) {
script = bitcoin.payments.p2wpkh({network: currentNetwork, pubkey: pubkeyBuffer});
} else if (txType == 3) {
const childNodeXOnlyPubkey = toXOnly(pubkeyBuffer);
script = bitcoin.payments.p2tr({
network: currentNetwork,
internalPubkey: childNodeXOnlyPubkey,
});
}
const psbt = new bitcoin.Psbt({network: currentNetwork});
let {
total,
spending,
spendingUtxos,
size
} = btc.calcSpendingUtxosAndFee(mainnet, txType, utxos, receiveAddress, sendAmount, feeRate, opReturnArray);
for (let i = 0; i < opReturnArray.length; i++) {
let opReturnBuffer = opReturnArray[i];
psbt.addOutput({
script: bitcoin.payments.embed({data: [opReturnBuffer]}).output,
value: 0,
});
}
psbt.addOutput({
address: receiveAddress,
value: sendAmount,
});
if (total > spending && total - spending > 546) {
psbt.addOutput({
address: script.address,
value: total - spending,
});
}
return {psbt, currentNetwork, spendingUtxos, script};
}
function getBtcRpc(mainnet = false) {
let url = 'https://btctest.nerve.network';
let authValue = 'Basic TmVydmU6OW83ZlNtWFBCZlBRb1FTYm5CQg==';
if (mainnet) {
url = 'https://btc.nerve.network';
authValue = 'Basic TmVydmU6OW83ZlNtWFBCQ000RjZjQUpzZlBRb1FTYm5CQg==';
}
return {url, authValue};
}
async function getFeeRateOnChain(mainnet = false, highFeeRate = false) {
// if (!mainnet) {
// return 1;
// }
let mode = highFeeRate ? "CONSERVATIVE" : "ECONOMICAL";
let {url, authValue} = getBtcRpc(mainnet);
return await http.postCompleteWithHeader(
url,
'estimatesmartfee',
[15, mode],
{'Authorization': authValue}
).then((response) => {
if (response.hasOwnProperty("result")) {
return Math.ceil(response.result.feerate * 1.1 * 100000)
} else {
throw "Get rawtransaction error"
}
}).catch((error) => {
throw "Network error"
});
}
function getMempoolSpaceRpc(mainnet = false) {
let url = 'https://mempool.space/testnet/api/v1/';
if (mainnet) {
url = 'https://mempool.space/api/v1/';
}
return url;
}
function checkUInt53(n) {
if (n < 0 || n > MAX_SAFE_INTEGER || n % 1 !== 0) throw new RangeError('value out of range')
}
function encodingLength(number) {
checkUInt53(number)
return (
number < 0xfd ? 1
: number <= 0xffff ? 3
: number <= 0xffffffff ? 5
: 9
)
}
function calcOpReturnLen(opReturnBytesLen) {
let dataLen;
if (opReturnBytesLen < 76) {
dataLen = opReturnBytesLen + 1;
} else if (opReturnBytesLen < 256) {
dataLen = opReturnBytesLen + 2;
} else dataLen = opReturnBytesLen + 3;
let scriptLen;
scriptLen = (dataLen + 1) + encodingLength(dataLen + 1);
let amountLen = 8;
return scriptLen + amountLen;
}
/**
* calc split number of Transaction Change Spliting by splitGranularity
* change = fromTotal - transfer - fee
* change = splitNum * splitGranularity
* fee = txSize * feeRate
* txSize = f(splitNum)
* f(splitNum) = f(1) + 43 * (splitNum - 1) ==> Derived from btc.calcTxSizeWithdrawal(inputNum, splitNum)
* In summary:
* splitNum = (fromTotal - transfer - btc.calcTxSizeWithdrawal(inputNum, 1) * feeRate + 43 * feeRate) / (43 * feeRate + splitGranularity)
*/
function calcSplitNumP2WSH(fromTotal, transfer, feeRate, splitGranularity, inputNum) {
// numerator and denominator
const numerator = fromTotal - transfer - btc.calcTxSizeWithdrawal(inputNum, 1) * feeRate + 43 * feeRate;
const denominator = 43 * feeRate + splitGranularity;
let splitNum = (int) (numerator / denominator);
if (splitNum == 0 && numerator % denominator > 0) {
splitNum = 1;
}
return splitNum;
}
var btc = {
initEccLibForNode() {
const ecc = require('tiny-secp256k1');
ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);
return ECPair;
},
initEccLibForWeb() {
require('tiny-secp256k1').then(ecc => {
ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);
});
},
getAddressByPub(pubKey, isMainnet) {
const network = isMainnet ? bitcoin.networks.bitcoin : bitcoin.networks.testnet;
const publicKeyBuffer = Buffer.from(pubKey, 'hex');
// p2pkh Legacy
const {address: Legacy} = bitcoin.payments.p2pkh({network, pubkey: publicKeyBuffer});
// p2wpkh Native Segwit
const {address: NativeSegwit} = bitcoin.payments.p2wpkh({network, pubkey: publicKeyBuffer});
// p2sh Nested Segwit
const {address: NestedSegwit} = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({network, pubkey: publicKeyBuffer}),
});
// p2tr Taproot
const {address: Taproot} = bitcoin.payments.p2tr({
network,
internalPubkey: toXOnly(publicKeyBuffer)
});
return {
Legacy,
NativeSegwit,
NestedSegwit,
Taproot
}
},
calcSpendingUtxosAndFee(mainnet, txType, utxos, receiveAddress, sendAmount, feeRate, opReturnArray) {
let len0 = 0, len1 = 0, len2 = 0, len3 = 0;
let outForType = 'len' + txType + '++';
eval(outForType);
let add = bitcore.Address.fromString(receiveAddress, mainnet ? 'livenet' : 'testnet');
if (add.isPayToPublicKeyHash()) {
len0++;
} else if (add.isPayToScriptHash()) {
len1++;
} else if (add.isPayToWitnessPublicKeyHash() || add.isPayToWitnessScriptHash()) {
len2++;
} else if (add.isPayToTaproot()) {
len3++;
}
const nativeWitnessScriptOutCount = add.isPayToWitnessScriptHash() ? 1 : 0;
let fee = 0;
let size = 0;
let total = 0;
let enoughUTXO = false;
let spendingUtxos = [];
for (let k = 0; k < utxos.length; k++) {
let utxo = utxos[k];
spendingUtxos.push(utxo);
total += Number(utxo.amount);
size = this.estimateTxSize(txType, spendingUtxos.length, len0, len1, len2, len3, opReturnArray, nativeWitnessScriptOutCount);
fee = size * feeRate;
if (total >= (sendAmount + fee)) {
enoughUTXO = true;
break;
}
}
if (!enoughUTXO) {
throw "not enough utxo, may need more: " + (sendAmount + fee - total);
}
let spending = sendAmount + fee;
/*let size = this.estimateTxSize(txType, 1, len0, len1, len2, len3, opReturnArray, nativeWitnessScriptOutCount);
let fee = size * feeRate;
let spending = sendAmount + fee;
let {spendingUtxos, total, inputCount} = getSpendingUtxos(utxos, spending);
if (inputCount > 1) {
let _size = this.estimateTxSize(txType, inputCount, len0, len1, len2, len3, opReturnArray, nativeWitnessScriptOutCount);
fee = _size * feeRate;
spending = sendAmount + fee;
size = _size;
while (total < spending) {
let {
spendingUtxos: spendingUtxos1,
total: total1,
inputCount: inputCount1
} = getSpendingUtxos(utxos, spending);
if (inputCount1 > inputCount) {
let size1 = this.estimateTxSize(txType, inputCount1, len0, len1, len2, len3, opReturnArray, nativeWitnessScriptOutCount);
fee = size1 * feeRate;
spending = sendAmount + fee;
size = size1;
}
total = total1;
inputCount = inputCount1;
spendingUtxos = spendingUtxos1;
}
}
return {total, spending, spendingUtxos, size, fee};
*/
return {total, spending, spendingUtxos, size, fee};
},
estimateTxSize(txType, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray, nativeSegwitScriptOutputCount) {
let size = 0;
if (nativeSegwitScriptOutputCount) {
size += nativeSegwitScriptOutputCount * 12;
}
switch (txType) {
case 0:
size += estimateLegacyTxSize(false, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
break;
case 1:
size += estimateNestedSegwitTxSize(false, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
break;
case 2:
size += estimateNativeSegwitTxSize(false, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
break;
case 3:
size += estimateTaprootTxSize(false, inputCount, legacyOutputCount, nestedSegwitOutputCount, nativeSegwitOutputCount, taprootOutputCount, opReturnArray);
break;
default:
return 0;
}
return size;
},
async getrawtransaction(mainnet = false, txid, verbose = false) {
let {url, authValue} = getBtcRpc(mainnet);
return await http.postCompleteWithHeader(
url,
'getrawtransaction',
[txid, verbose],
{'Authorization': authValue}
).then((response) => {
if (response.hasOwnProperty("result")) {
return response.result
} else {
throw "Get rawtransaction error"
}
}).catch((error) => {
throw "Network error"
});
},
async getFeeRate(mainnet = false, highFeeRate = true) {
let mode = highFeeRate ? "CONSERVATIVE" : "ECONOMICAL";
let url = getMempoolSpaceRpc(mainnet);
url += 'fees/recommended';
return await http.get(url).then((response) => {
if (response.hasOwnProperty("fastestFee")) {
return highFeeRate ? response.fastestFee : response.halfHourFee;
} else {
throw "Get recommended fee error: " + response
}
}).catch((error) => {
throw "Network error: " + error
});
},
async getUtxos(mainnet = false, address, amount) {
if (!amount) {
amount = 0;
}
if (mainnet) {
return await http.postDirect(
'https://api.v2.nabox.io/nabox-api/btc/utxo',
{
"language": "CHS",
"chainId": 0,
"address": address,
"coinAmount": amount,
"serviceCharge": amount,
"utxoType": 1
}
).then((response) => {
if (response.hasOwnProperty("code")) {
if (response.code !== 1000) {
throw response.msg;
}
let utxos = response.data.utxo;
for (let i = 0; i < utxos.length; i++) {
let utxo = utxos[i];
utxo.amount = utxo.satoshi;
}
return utxos
} else {
throw "Get utxo error"
}
}).catch((error) => {
throw "Network error"
});
}
return await http.get(
'https://mempool.space/testnet/api/address/' + address + '/utxo'
).then((response) => {
if (typeof response === 'object') {
let utxos = response;
for (let i = 0; i < utxos.length; i++) {
let utxo = utxos[i];
utxo.amount = utxo.value;
}
return utxos
} else {
console.log(typeof response, response);
throw "Get utxo error";
}
}).catch((error) => {
throw "Network error"
});
},
checkAddressType(mainnet = false, address) {
let add = bitcore.Address.fromString(address, mainnet ? 'livenet' : 'testnet');
if (add.isPayToPublicKeyHash()) {
return 0;
} else if (add.isPayToScriptHash()) {
return 1;
} else if (add.isPayToWitnessPublicKeyHash() || add.isPayToWitnessScriptHash()) {
return 2;
} else if (add.isPayToTaproot()) {
return 3;
}
return -1;
},
async createLegacyTx(mainnet = false, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray, isRBF) {
const {
psbt,
currentNetwork,
spendingUtxos,
script
} = createSpendingUtxosAndOutput(mainnet, 0, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray);
let _SEQUENCE = bitcoin.Transaction.DEFAULT_SEQUENCE;
if (isRBF) _SEQUENCE -= 2;
for (let i = 0; i < spendingUtxos.length; i++) {
let utxo = spendingUtxos[i];
let txHex = await this.getrawtransaction(mainnet, utxo.txid);
utxo.txHex = txHex;
psbt.addInput({
hash: utxo.txid,
index: Number(utxo.vout),
nonWitnessUtxo: Buffer.from(utxo.txHex, 'hex'),
sequence: _SEQUENCE
});
}
const psbtHex = psbt.toHex();
return psbtHex;
},
createNestedSegwitTx(mainnet = false, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray, isRBF) {
const {
psbt,
currentNetwork,
spendingUtxos,
script
} = createSpendingUtxosAndOutput(mainnet, 1, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray);
let _SEQUENCE = bitcoin.Transaction.DEFAULT_SEQUENCE;
if (isRBF) _SEQUENCE -= 2;
for (let i = 0; i < spendingUtxos.length; i++) {
let utxo = spendingUtxos[i];
psbt.addInput({
hash: utxo.txid,
index: Number(utxo.vout),
witnessUtxo: {script: script.output, value: utxo.amount},
redeemScript: script.redeem.output,
sequence: _SEQUENCE
});
}
const psbtHex = psbt.toHex();
return psbtHex;
},
createNativeSegwitTx(mainnet = false, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray, isRBF) {
const {
psbt,
currentNetwork,
spendingUtxos,
script
} = createSpendingUtxosAndOutput(mainnet, 2, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray);
let _SEQUENCE = bitcoin.Transaction.DEFAULT_SEQUENCE;
if (isRBF) _SEQUENCE -= 2;
for (let i = 0; i < spendingUtxos.length; i++) {
let utxo = spendingUtxos[i];
psbt.addInput({
hash: utxo.txid,
index: Number(utxo.vout),
witnessUtxo: {script: script.output, value: utxo.amount},
sequence: _SEQUENCE
});
}
const psbtHex = psbt.toHex();
return psbtHex;
},
createTaprootTx(mainnet = false, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray, isRBF) {
const {
psbt,
currentNetwork,
spendingUtxos,
script
} = createSpendingUtxosAndOutput(mainnet, 3, pubkeyHex, utxos, receiveAddress, sendAmount, feeRate, opReturnArray);
let _SEQUENCE = bitcoin.Transaction.DEFAULT_SEQUENCE;
if (isRBF) _SEQUENCE -= 2;
const childNodeXOnlyPubkey = toXOnly(Buffer.from(pubkeyHex, 'hex'));
for (let i = 0; i < spendingUtxos.length; i++) {
let utxo = spendingUtxos[i];
psbt.addInput({
hash: utxo.txid,
index: Number(utxo.vout),
witnessUtxo: {script: script.output, value: utxo.amount},
tapInternalKey: childNodeXOnlyPubkey,
sequence: _SEQUENCE
});
}
const psbtHex = psbt.toHex();
return psbtHex;
},
calcMultiSignSizeP2WSH(inputNum, outputNum, opReturnBytesLenArray, m, n) {
let redeemScriptLength = 1 + (n * 33) + 1 + 1;
let scriptLength = 1 + (m * (1 + 1 + 69 + 1)) + encodingLength(redeemScriptLength) + redeemScriptLength;
let inputLength = 40 + (scriptLength / 4 + 1);
let length;
if (!opReturnBytesLenArray || opReturnBytesLenArray.length == 0) {
length = 12 + inputLength * inputNum + 43 * (outputNum + 1);
} else {
let totalOpReturnLen = 0;
let arrayLength = opReturnBytesLenArray.length;
for (let i = 0; i < arrayLength; i++) {
let len = opReturnBytesLenArray[i];
totalOpReturnLen += calcOpReturnLen(len);
}
length = 12 + inputLength * inputNum + 43 * (outputNum + 1) + totalOpReturnLen;
}
return length;
},
calcTxSizeWithdrawal(utxoSize, outputNum = 1) {
let m, n;
if (nerve.chainId() == 9) {
m = 10, n = 15;
} else {
m = 2, n = 3;
}
return nerve.bitcoin.calcMultiSignSizeP2WSH(utxoSize, outputNum, [32], m, n);
},
calcFeeWithdrawal(utxos, amount, feeRate, splitGranularity = 0) {
if (Array.isArray(utxos)) {
utxos.sort((a, b) => {
if (a.value !== b.value) {
return a.value < b.value ? -1 : 1;
} else if (a.txid !== b.txid) {
return a.txid < b.txid ? -1 : 1;
} else if (a.vout !== b.vout) {
return Number(a.vout) < Number(b.vout) ? -1 : 1;
} else {
return 0;
}
});
}
let _fee = 0, total = 0, totalSpend = 0;
let resultList = [];
amount = Number(amount)
for (let i = 0; i < utxos.length; i++) {
let utxo = utxos[i];
total = total + utxo.amount;
resultList.push(utxo);
if (splitGranularity > 0) {
const splitNum = calcSplitNumP2WSH(total, amount, feeRate, splitGranularity, resultList.length);
_fee = this.calcTxSizeWithdrawal(resultList.length, splitNum) * feeRate;
} else {
_fee = this.calcTxSizeWithdrawal(resultList.length) * feeRate;
}
totalSpend = amount + _fee;
if (total >= totalSpend) {
break;
}
}
if (total < totalSpend) {
throw "not enough fee";
}
return _fee;
},
isValidBTCAddress(mainnet = false, address) {
try {
const network = !mainnet ? bitcoin?.networks.testnet : bitcoin?.networks.bitcoin;
bitcoin.address.toOutputScript(address, network);
return true;
} catch (e) {
return false;
}
}
}
module.exports = btc;