@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
JavaScript
;
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;
}