@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.
67 lines (66 loc) • 3.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.addUntilReach = addUntilReach;
const index_1 = require("../index");
const validation_1 = require("../validation");
const vsize_1 = require("../vsize");
const dust_1 = require("../dust");
/**
* The `addUntilReach` algorithm is similar to the default {@link coinselect coinselect}.
* It continuously adds UTXOs until the combined value surpasses the sum of the targets and fees.
* This function does not reorder UTXOs before selection. It evaluates whether creating change
* is feasible, with consideration of whether the change exceeds the dust threshold.
*
* Notes:
*
* - This function does not reorder UTXOs prior to selection.
* - UTXOs that do not provide enough value to cover their respective fee contributions are automatically excluded.
*
* Refer to {@link coinselect coinselect} for additional details on input parameters and expected returned values.
*/
function addUntilReach({ utxos, targets, remainder, feeRate, minimumFeeRate = index_1.MIN_FEE_RATE, dustRelayFeeRate = index_1.DUST_RELAY_FEE_RATE }) {
(0, validation_1.validateOutputWithValues)(utxos);
(0, validation_1.validateOutputWithValues)(targets);
(0, validation_1.validateDust)(targets);
(0, validation_1.validateFeeRate)(feeRate, minimumFeeRate);
(0, validation_1.validateFeeRate)(dustRelayFeeRate);
const targetsValue = targets.reduce((a, target) => a + target.value, 0n);
const utxosSoFar = [];
for (const candidate of utxos) {
const txSizeSoFar = (0, vsize_1.vsize)(utxosSoFar.map(utxo => utxo.output), targets.map(target => target.output));
const utxosSoFarValue = utxosSoFar.reduce((a, utxo) => a + utxo.value, 0n);
const txFeeSoFar = BigInt(Math.ceil(txSizeSoFar * feeRate));
const txSizeWithCandidate = (0, vsize_1.vsize)([candidate.output, ...utxosSoFar.map(utxo => utxo.output)], targets.map(target => target.output));
const txFeeWithCandidate = BigInt(Math.ceil(txSizeWithCandidate * feeRate));
const candidateFeeContribution = txFeeWithCandidate - txFeeSoFar;
if (candidateFeeContribution < 0n)
throw new Error(`candidateFeeContribution < 0`);
// Only consider inputs with more value than the fee they require
if (candidate.value > candidateFeeContribution) {
if (utxosSoFarValue + candidate.value >=
targetsValue + txFeeWithCandidate) {
// Evaluate if adding remainder is beneficial
const txSizeWithCandidateAndChange = (0, vsize_1.vsize)([candidate.output, ...utxosSoFar.map(utxo => utxo.output)], [remainder, ...targets.map(target => target.output)]);
const txFeeWithCandidateAndChange = BigInt(Math.ceil(txSizeWithCandidateAndChange * feeRate));
const remainderValue = utxosSoFarValue +
candidate.value -
(targetsValue + txFeeWithCandidateAndChange);
//return the same reference if nothing changed to interact nicely with
//reactive components
const utxosResult = [candidate, ...utxosSoFar];
const targetsResult = (0, dust_1.isDust)(remainder, remainderValue, dustRelayFeeRate)
? targets
: [...targets, { output: remainder, value: remainderValue }];
return {
utxos: utxosResult.length === utxos.length ? utxos : utxosResult,
targets: targetsResult,
...(0, validation_1.validatedFeeAndVsize)(utxosResult, targetsResult, feeRate, minimumFeeRate)
};
}
else {
utxosSoFar.push(candidate);
}
}
}
return;
}