@trezor/utxo-lib
Version:
Client-side Bitcoin-like JavaScript library
234 lines • 8.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MINIMAL_COINBASE_CONFIRMATIONS = exports.OUTPUT_SCRIPT_LENGTH = exports.INPUT_SCRIPT_LENGTH = exports.ZERO = void 0;
exports.getVarIntSize = getVarIntSize;
exports.inputWeight = inputWeight;
exports.inputBytes = inputBytes;
exports.outputWeight = outputWeight;
exports.outputBytes = outputBytes;
exports.getFeeForBytes = getFeeForBytes;
exports.transactionWeight = transactionWeight;
exports.transactionBytes = transactionBytes;
exports.getDustAmount = getDustAmount;
exports.bignumberOrNaN = bignumberOrNaN;
exports.sumOrNaN = sumOrNaN;
exports.getFeePolicy = getFeePolicy;
exports.getFee = getFee;
exports.finalize = finalize;
exports.anyOf = anyOf;
exports.utxoScore = utxoScore;
exports.sortByScore = sortByScore;
exports.filterCoinbase = filterCoinbase;
const tslib_1 = require("tslib");
const bn_js_1 = tslib_1.__importDefault(require("bn.js"));
const networks_1 = require("../networks");
exports.ZERO = new bn_js_1.default(0);
exports.INPUT_SCRIPT_LENGTH = {
p2pkh: 108,
p2sh: 107,
p2tr: 65,
p2wpkh: 107,
p2wsh: 107,
};
exports.OUTPUT_SCRIPT_LENGTH = {
p2pkh: 25,
p2sh: 23,
p2tr: 34,
p2wpkh: 22,
p2wsh: 34,
};
const SEGWIT_INPUT_SCRIPT_TYPES = ['p2sh', 'p2tr', 'p2wpkh', 'p2wsh'];
const TX_BASE = 32;
const INPUT_SIZE = 160;
const DUST_RELAY_FEE_RATE = 3;
exports.MINIMAL_COINBASE_CONFIRMATIONS = 100;
function getVarIntSize(length) {
if (length < 253)
return 1;
if (length < 65536)
return 3;
if (length < 4294967296)
return 5;
return 9;
}
function inputWeight(input) {
if (input.weight)
return input.weight;
let weight = INPUT_SIZE;
if (!SEGWIT_INPUT_SCRIPT_TYPES.includes(input.type)) {
weight += 4 * input.script.length;
}
else {
if (input.type === 'p2sh') {
weight += 4 * (2 + 22);
}
else {
weight += 4;
}
weight += 1 + input.script.length;
}
return weight;
}
function inputBytes(input) {
return Math.ceil(inputWeight(input) / 4);
}
function outputWeight(output) {
if (output.weight)
return output.weight;
return 4 * (8 + 1 + output.script.length);
}
function outputBytes(output) {
return Math.ceil(outputWeight(output) / 4);
}
function getFeeForBytes(feeRate, bytes) {
return Math.ceil(feeRate * bytes);
}
function transactionWeight(inputs, outputs) {
const segwitInputs = inputs.filter(i => SEGWIT_INPUT_SCRIPT_TYPES.includes(i.type)).length;
return (TX_BASE +
4 * getVarIntSize(inputs.length) +
inputs.reduce((x, i) => x + inputWeight(i), 0) +
4 * getVarIntSize(outputs.length) +
outputs.reduce((x, o) => x + outputWeight(o), 0) +
(segwitInputs ? 2 + (inputs.length - segwitInputs) : 0));
}
function transactionBytes(inputs, outputs) {
return Math.ceil(transactionWeight(inputs, outputs) / 4);
}
function getDustAmount(feeRate, { txType, longTermFeeRate, dustThreshold, }) {
const inputSize = inputBytes({
type: txType,
script: {
length: exports.INPUT_SCRIPT_LENGTH[txType],
},
});
const longTermFee = longTermFeeRate ? Math.min(feeRate, longTermFeeRate) : 0;
const dustRelayFeeRate = Math.max(longTermFee, DUST_RELAY_FEE_RATE);
return Math.max(dustThreshold || 0, getFeeForBytes(dustRelayFeeRate, inputSize));
}
function bignumberOrNaN(v, forgiving = false) {
if (bn_js_1.default.isBN(v))
return v;
const defaultValue = forgiving ? exports.ZERO : undefined;
if (!v || typeof v !== 'string' || !/^\d+$/.test(v))
return defaultValue;
try {
return new bn_js_1.default(v);
}
catch {
return defaultValue;
}
}
function sumOrNaN(range, forgiving = false) {
return range.reduce((a, x) => {
if (!a)
return a;
const value = bignumberOrNaN(x.value);
if (!value)
return forgiving ? exports.ZERO.add(a) : undefined;
return value.add(a);
}, exports.ZERO);
}
function getFeePolicy(network) {
if ((0, networks_1.isNetworkType)('doge', network))
return 'doge';
if ((0, networks_1.isNetworkType)('zcash', network))
return 'zcash';
return 'bitcoin';
}
function getBitcoinFee(inputs, outputs, feeRate, { baseFee = 0, floorBaseFee }) {
const bytes = transactionBytes(inputs, outputs);
const defaultFee = getFeeForBytes(feeRate, bytes);
return baseFee && floorBaseFee
?
baseFee * (1 + Math.floor(defaultFee / baseFee))
:
baseFee + defaultFee;
}
function getDogeFee(inputs, outputs, feeRate, { dustThreshold = 0, ...options }) {
const fee = getBitcoinFee(inputs, outputs, feeRate, options);
const limit = new bn_js_1.default(dustThreshold);
const dustOutputsCount = outputs.filter(({ value }) => value && value.lt(limit)).length;
return fee + dustOutputsCount * dustThreshold;
}
const MARGINAL_FEE_ZAT_PER_ACTION = 5000;
const GRACE_ACTIONS = 2;
const P2PKH_STANDARD_INPUT_SIZE = 150;
const P2PKH_STANDARD_OUTPUT_SIZE = 34;
function getZcashFee(inputs, outputs, feeRate, options) {
const fee = getBitcoinFee(inputs, outputs, feeRate, options);
const txInTotalBytes = inputs.reduce((sum, i) => sum + inputBytes(i), 0);
const txOutTotalBytes = outputs.reduce((sum, o) => sum + outputBytes(o), 0);
const actions = Math.max(GRACE_ACTIONS, Math.ceil(txInTotalBytes / P2PKH_STANDARD_INPUT_SIZE), Math.ceil(txOutTotalBytes / P2PKH_STANDARD_OUTPUT_SIZE));
return Math.max(actions * MARGINAL_FEE_ZAT_PER_ACTION, fee);
}
function getFee(inputs, outputs, feeRate, { feePolicy, ...options } = {}) {
switch (feePolicy) {
case 'doge':
return getDogeFee(inputs, outputs, feeRate, options);
case 'zcash':
return getZcashFee(inputs, outputs, feeRate, options);
default:
return getBitcoinFee(inputs, outputs, feeRate, options);
}
}
function finalize(inputs, outputs, feeRate, options) {
const changeOutput = options.changeOutput || {
script: { length: exports.OUTPUT_SCRIPT_LENGTH[options.txType] },
};
const fee = getFee(inputs, outputs, feeRate, options);
const feeAfterExtraOutput = getFee(inputs, [...outputs, changeOutput], feeRate, options);
const sumInputs = sumOrNaN(inputs);
const sumOutputs = sumOrNaN(outputs);
if (!sumInputs || !sumOutputs || sumInputs.sub(sumOutputs).lt(new bn_js_1.default(fee))) {
return { fee };
}
const remainderAfterExtraOutput = sumInputs.sub(sumOutputs.add(new bn_js_1.default(feeAfterExtraOutput)));
const dustAmount = getDustAmount(feeRate, options);
const finalOutputs = [...outputs];
if (remainderAfterExtraOutput.gte(new bn_js_1.default(dustAmount))) {
finalOutputs.push({
...changeOutput,
value: remainderAfterExtraOutput,
});
}
return {
inputs,
outputs: finalOutputs,
fee: sumInputs.sub(sumOrNaN(finalOutputs, true)).toNumber(),
};
}
function anyOf(algorithms) {
return (utxos, outputs, feeRate, options) => {
let result = { fee: 0 };
for (let i = 0; i < algorithms.length; i++) {
const algorithm = algorithms[i];
result = algorithm(utxos, outputs, feeRate, options);
if (result.inputs) {
return result;
}
}
return result;
};
}
function utxoScore(x, feeRate) {
return x.value.sub(new bn_js_1.default(getFeeForBytes(feeRate, inputBytes(x))));
}
function sortByScore(feeRate) {
return (a, b) => {
const difference = utxoScore(a, feeRate).sub(utxoScore(b, feeRate));
if (difference.eq(exports.ZERO)) {
return a.i - b.i;
}
return difference.isNeg() ? 1 : -1;
};
}
function filterCoinbase(utxos, minConfCoinbase) {
return utxos.filter(utxo => {
if (utxo.coinbase && !utxo.required) {
return utxo.confirmations >= minConfCoinbase;
}
return true;
});
}
//# sourceMappingURL=coinselectUtils.js.map