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.

67 lines (66 loc) 3.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.avoidChange = avoidChange; 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, 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 utxosSoFarValue = utxosSoFar.reduce((a, utxo) => a + utxo.value, 0n); 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); 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 txSizeSoFar = (0, vsize_1.vsize)(utxosSoFar.map(utxo => utxo.output), targets.map(target => target.output)); const txFeeSoFar = BigInt(Math.ceil(txSizeSoFar * 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) { //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, minimumFeeRate) }; } else utxosSoFar.push(candidate); } } } return; }