UNPKG

@bitcoinerlab/coinselect

Version:

A TypeScript library for Bitcoin transaction management, based on Bitcoin Descriptors for defining inputs and outputs. It facilitates optimal UTXO selection and transaction size calculation.

106 lines (105 loc) 4.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.coinselect = coinselect; const index_1 = require("./index"); const validation_1 = require("./validation"); const addUntilReach_1 = require("./algos/addUntilReach"); const avoidChange_1 = require("./algos/avoidChange"); const vsize_1 = require("./vsize"); // order by descending value, minus the inputs approximate fee function utxoTransferredValue(outputAndValue, feeRate, isSegwitTx) { return (Number(outputAndValue.value) - (feeRate * outputAndValue.output.inputWeight(isSegwitTx, 'DANGEROUSLY_USE_FAKE_SIGNATURES')) / 4); } /** * Selects UTXOs for a Bitcoin transaction. * * Sorts UTXOs by their descending net value * (each UTXO's value minus the fees needed to spend it). * * It initially attempts to find a solution using the * {@link avoidChange avoidChange} algorithm, * which aims to select UTXOs such that no change is required. If this is not * possible, it then applies the {@link addUntilReach addUntilReach} algorithm, * which adds UTXOs * until the total value exceeds the target value plus fees. * Change is added only if it's above the {@link dustThreshold dustThreshold}). * * UTXOs that do not provide enough value to cover their respective fee * contributions are automatically excluded. * * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH` * (Script Hash-Witness Public Key Hash). * For inputs using arbitrary scripts (not standard addresses), * use a descriptor in the format `sh(MINISCRIPT)`. * * @returns Object with selected UTXOs, targets, fee, and estimated vsize, or * undefined if no solution is found. * * @example * ``` * const { utxos, targets, fee, vsize } = coinselect({ * utxos: [ * { output: new Output({ descriptor: 'addr(...)' }), value: 2000n }, * { output: new Output({ descriptor: 'addr(...)' }), value: 4000n } * ], * targets: [ * { output: new Output({ descriptor: 'addr(...)' }), value: 3000n } * ], * remainder: new Output({ descriptor: 'addr(...)' }), * feeRate: 1.34 * }); * ``` * * @see {@link https://bitcoinerlab.com/modules/descriptors} for descriptor info. */ function coinselect({ utxos, targets, remainder, feeRate, minimumFeeRate = index_1.MIN_FEE_RATE, dustRelayFeeRate = index_1.DUST_RELAY_FEE_RATE }) { (0, validation_1.validateOutputWithValues)(utxos); if (targets) { (0, validation_1.validateOutputWithValues)(targets); (0, validation_1.validateDust)(targets); } (0, validation_1.validateFeeRate)(feeRate, minimumFeeRate); (0, validation_1.validateFeeRate)(dustRelayFeeRate); //We will assume that the tx is segwit if there is at least one segwit //utxo for computing the utxo ordering. This is an approximation. //Note that having one segwit utxo does not mean the final tx will be segwit //(because the coinselect algo may end up choosing only non-segwit utxos). const isPossiblySegwitTx = (0, vsize_1.isSegwitTx)(utxos.map(utxo => utxo.output)); //Sort in descending utxoTransferredValue //Using [...utxos] because sort mutates the input const sortedUtxos = [...utxos].sort((a, b) => utxoTransferredValue(b, feeRate, isPossiblySegwitTx) - utxoTransferredValue(a, feeRate, isPossiblySegwitTx)); const coinselected = (0, avoidChange_1.avoidChange)({ utxos: sortedUtxos, targets, remainder, feeRate, minimumFeeRate, dustRelayFeeRate }) || (0, addUntilReach_1.addUntilReach)({ utxos: sortedUtxos, targets, remainder, feeRate, minimumFeeRate, dustRelayFeeRate }); if (coinselected) { //return the same reference if nothing changed to interact nicely with //reactive components utxos = coinselected.utxos.length === utxos.length ? utxos : coinselected.utxos; targets = coinselected.targets.length === targets?.length ? targets : coinselected.targets; return { utxos, targets, fee: coinselected.fee, vsize: coinselected.vsize }; } else return; }