@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.
68 lines (67 loc) • 3.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.avoidChange = void 0;
const index_1 = require("../index");
const validation_1 = require("../validation");
const vsize_1 = require("../vsize");
const dust_1 = require("../dust");
/**
* Seeks a selection of UTXOs that does not necessitate the creation of change.
* Although the function signature matches that of the standard {@link coinselect coinselect},
* requiring a `remainder`, change is never generated. The `remainder` is used
* to assess if hypothetical change would NOT be considered dust, thereby rendering
* the solution unviable.
*
* 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 avoidChange({ utxos, targets, remainder, feeRate, 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);
(0, validation_1.validateFeeRate)(dustRelayFeeRate);
const targetsValue = targets.reduce((a, target) => a + target.value, 0);
const utxosSoFar = [];
for (const candidate of utxos) {
const utxosSoFarValue = utxosSoFar.reduce((a, utxo) => a + utxo.value, 0);
const txSizeWithCandidateAndChange = (0, vsize_1.vsize)([candidate.output, ...utxosSoFar.map(utxo => utxo.output)], [remainder, ...targets.map(target => target.output)]);
const txFeeWithCandidateAndChange = Math.ceil(txSizeWithCandidateAndChange * feeRate);
const remainderValue = utxosSoFarValue +
candidate.value -
(targetsValue + txFeeWithCandidateAndChange);
const txSizeWithCandidate = (0, vsize_1.vsize)([candidate.output, ...utxosSoFar.map(utxo => utxo.output)], targets.map(target => target.output));
const txFeeWithCandidate = Math.ceil(txSizeWithCandidate * feeRate);
const txSizeSoFar = (0, vsize_1.vsize)(utxosSoFar.map(utxo => utxo.output), targets.map(target => target.output));
const txFeeSoFar = Math.ceil(txSizeSoFar * feeRate);
const candidateFeeContribution = txFeeWithCandidate - txFeeSoFar;
if (candidateFeeContribution < 0)
throw new Error(`candidateFeeContribution < 0`);
// Only consider inputs with more value than the fee they require
if (candidate.value > candidateFeeContribution) {
//Check that adding the candidate utxo would NOT imply that change was needed:
if ((0, dust_1.isDust)(remainder, remainderValue, dustRelayFeeRate)) {
//Enough utxo value already so that it covers targets and fee?
if (utxosSoFarValue + candidate.value >=
targetsValue + txFeeWithCandidate) {
//return the same reference if nothing changed to interact nicely with
//reactive components
const utxosResult = [candidate, ...utxosSoFar];
return {
utxos: utxosResult.length === utxos.length ? utxos : utxosResult,
targets,
...(0, validation_1.validatedFeeAndVsize)(utxosResult, targets, feeRate)
};
}
else
utxosSoFar.push(candidate);
}
}
}
return;
}
exports.avoidChange = avoidChange;