UNPKG

bitcoin-utxo-select

Version:

Bitcoin utxo selections

129 lines (128 loc) 5.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.finalize = exports.sumValues = exports.BLANK_OUTPUT = exports.transactionBytes = exports.dustThreshold = exports.sortUtxoBasedOnScore = exports.utxoScore = exports.outputBytes = exports.inputBytes = exports.TX_OUTPUT_SEGWIT_SCRIPTHASH = exports.TX_OUTPUT_SEGWIT = exports.TX_OUTPUT_SCRIPTHASH = exports.TX_OUTPUT_PUBKEYHASH = exports.TX_OUTPUT_BASE = exports.TX_INPUT_TAPROOT = exports.TX_INPUT_SEGWIT = exports.TX_INPUT_PUBKEYHASH = exports.TX_INPUT_BASE = exports.TX_EMPTY_SIZE = void 0; // Refer to this page: https://bitcoinops.org/en/tools/calc-size/ exports.TX_EMPTY_SIZE = 4 + 1 + 1 + 4; exports.TX_INPUT_BASE = 32 + 4 + 1 + 4; exports.TX_INPUT_PUBKEYHASH = 107; exports.TX_INPUT_SEGWIT = 27 + 1; exports.TX_INPUT_TAPROOT = 17 + 1; exports.TX_OUTPUT_BASE = 8 + 1; exports.TX_OUTPUT_PUBKEYHASH = 25; exports.TX_OUTPUT_SCRIPTHASH = 23; exports.TX_OUTPUT_SEGWIT = 22; exports.TX_OUTPUT_SEGWIT_SCRIPTHASH = 34; // TODO: make it more secure and add some validations function inputBytes(input) { let bytes = exports.TX_INPUT_BASE; if (input.redeemScript) { bytes += input.redeemScript.length; } if (input.witnessScript) { bytes += Math.ceil(input.witnessScript.byteLength / 4); } else if (input.isTaproot) { if (input.taprootWitness) { bytes += Math.ceil(exports.TX_INPUT_TAPROOT + input.taprootWitness.reduce((prev, buffer) => prev + buffer.byteLength, 0) / 4); } else { bytes += exports.TX_INPUT_TAPROOT; } } else if (input.witnessUtxo) { bytes += exports.TX_INPUT_SEGWIT; } else if (!input.redeemScript) { bytes += exports.TX_INPUT_PUBKEYHASH; } return bytes; } exports.inputBytes = inputBytes; function outputBytes(output) { var _a, _b, _c, _d, _e, _f, _g; let bytes = exports.TX_OUTPUT_BASE; if (output.script) { bytes += output.script.byteLength; } else if (((_a = output.address) === null || _a === void 0 ? void 0 : _a.startsWith('bc1')) || // mainnet ((_b = output.address) === null || _b === void 0 ? void 0 : _b.startsWith('tb1')) || // testnet ((_c = output.address) === null || _c === void 0 ? void 0 : _c.startsWith('bcrt1')) // regtest ) { // 42 for mainnet/testnet, 44 for regtest if (((_d = output.address) === null || _d === void 0 ? void 0 : _d.length) === 42 || ((_e = output.address) === null || _e === void 0 ? void 0 : _e.length) === 44) { bytes += exports.TX_OUTPUT_SEGWIT; } else { // taproot fee approximate is same like p2wsh (2 of 3 multisig) bytes += exports.TX_OUTPUT_SEGWIT_SCRIPTHASH; } // both testnet and regtest has the same prefix 2 } else if (((_f = output.address) === null || _f === void 0 ? void 0 : _f.startsWith('3')) || ((_g = output.address) === null || _g === void 0 ? void 0 : _g.startsWith('2'))) { bytes += exports.TX_OUTPUT_SCRIPTHASH; } else { bytes += exports.TX_OUTPUT_PUBKEYHASH; } return bytes; } exports.outputBytes = outputBytes; // utxo minus the input approximate fee function utxoScore(utxo, feeRate) { return utxo.value - feeRate * inputBytes(utxo); } exports.utxoScore = utxoScore; // order by descending function sortUtxoBasedOnScore(utxos, feeRate) { return utxos.sort((a, b) => { return utxoScore(b, feeRate) - utxoScore(a, feeRate); }); } exports.sortUtxoBasedOnScore = sortUtxoBasedOnScore; function dustThreshold(feeRate) { // set the value from the highest input type return exports.TX_INPUT_BASE + exports.TX_INPUT_PUBKEYHASH * feeRate; } exports.dustThreshold = dustThreshold; function transactionBytes(inputs, outputs) { return (exports.TX_EMPTY_SIZE + inputs.reduce((prev, utxo) => prev + inputBytes(utxo), 0) + outputs.reduce((prev, target) => prev + outputBytes(target), 0)); } exports.transactionBytes = transactionBytes; // blank output for calcualting change fee exports.BLANK_OUTPUT = exports.TX_OUTPUT_BASE + exports.TX_OUTPUT_PUBKEYHASH; function sumValues(range) { return range.reduce((prev, v) => prev + (v.value || 0), 0); } exports.sumValues = sumValues; function finalize(inputs, outputs, feeRate, changeAddress, changeOutput) { let changeFee = exports.TX_OUTPUT_BASE + exports.TX_OUTPUT_PUBKEYHASH; if (changeAddress) { changeFee = outputBytes({ address: changeAddress }); } const bytesAccum = transactionBytes(inputs, outputs); const feeAfterExtraOutput = feeRate * (bytesAccum + changeFee); const remainderAfterExtraOutput = sumValues(inputs) - (sumValues(outputs) + feeAfterExtraOutput); if (changeOutput) { // is it worth a change output? if (remainderAfterExtraOutput > dustThreshold(feeRate)) { outputs = outputs.concat({ address: changeAddress, value: remainderAfterExtraOutput, }); } } var fee = sumValues(inputs) - sumValues(outputs); if (!isFinite(fee)) return { fee: feeRate * bytesAccum }; const txFee = transactionBytes(inputs, outputs) * feeRate; return { inputs, outputs, fee, txFee, }; } exports.finalize = finalize;