Skip to content

Commit d63073d

Browse files
fixup! fix: bitcoin input selector now tries to rescue dust if possible
1 parent 85719cb commit d63073d

File tree

2 files changed

+20
-18
lines changed

2 files changed

+20
-18
lines changed

packages/bitcoin/src/wallet/lib/input-selection/GreedyInputSelector.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { UTxO } from '../providers';
33
import { INPUT_SIZE, OUTPUT_SIZE, TRANSACTION_OVERHEAD, DUST_THRESHOLD } from '../common';
44
import { InputSelector } from './InputSelector';
55

6+
const ZERO = BigInt(0);
7+
68
/**
79
* A concrete implementation of InputSelector that uses a simple greedy algorithm.
810
*/
@@ -14,8 +16,8 @@ export class GreedyInputSelector implements InputSelector {
1416
feeRate: number
1517
): { selectedUTxOs: UTxO[]; outputs: { address: string; value: number }[]; fee: number } | undefined {
1618
const selected: UTxO[] = [];
17-
const totalOutput = outputs.reduce((acc, o) => acc + o.value, BigInt(0));
18-
let inputSum = BigInt(0);
19+
const totalOutput = outputs.reduce((acc, o) => acc + o.value, ZERO);
20+
let inputSum = ZERO;
1921
let fee = 0;
2022

2123
for (const utxo of utxos) {
@@ -29,7 +31,7 @@ export class GreedyInputSelector implements InputSelector {
2931

3032
let change = inputSum - totalOutput - BigInt(fee);
3133

32-
if (change > BigInt(0) && Number(change) < DUST_THRESHOLD) {
34+
if (change > ZERO && Number(change) < DUST_THRESHOLD) {
3335
const { change: newChange, fee: feeDelta } = this.attemptDustRescue({
3436
change,
3537
feeRate,
@@ -44,7 +46,7 @@ export class GreedyInputSelector implements InputSelector {
4446

4547
if (change < BigInt(DUST_THRESHOLD)) {
4648
fee += Number(change);
47-
change = BigInt(0);
49+
change = ZERO;
4850
}
4951
}
5052

@@ -64,7 +66,7 @@ export class GreedyInputSelector implements InputSelector {
6466
}
6567

6668
/**
67-
* Try to pull one more UtXO so that change exceeds the dust threshold if
69+
* Try to pull one more UTxO so that change exceeds the dust threshold if
6870
* the value rescued is worth more than the added fee.
6971
*/
7072
private attemptDustRescue({
@@ -84,7 +86,7 @@ export class GreedyInputSelector implements InputSelector {
8486
totalOutput: bigint;
8587
outputsCount: number;
8688
}): { change: bigint; fee: number } {
87-
if (change === BigInt(0) || Number(change) >= DUST_THRESHOLD) return { change, fee: 0 };
89+
if (change === ZERO || Number(change) >= DUST_THRESHOLD) return { change, fee: 0 };
8890

8991
const originalChange = change;
9092
let feeDelta = 0;

packages/bitcoin/test/GreedyInputSelector.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ describe('GreedyInputSelector', () => {
77
const feeRate = 1; // sat/byte
88
const changeAddress = 'change-address';
99

10-
const createUtXO = (satoshis: bigint, id = Math.random().toString()) => ({
10+
const createUTxO = (satoshis: bigint, id = Math.random().toString()) => ({
1111
txId: id,
1212
index: 0,
1313
address: 'address1',
1414
vout: 0,
1515
satoshis
1616
});
1717

18-
it('selects sufficient UtXOs and returns change', () => {
19-
const utxos = [createUtXO(BigInt(5000)), createUtXO(BigInt(8000))];
18+
it('selects sufficient UTxOs and returns change', () => {
19+
const utxos = [createUTxO(BigInt(5000)), createUTxO(BigInt(8000))];
2020
const outputs = [{ address: 'address1', value: BigInt(4000) }];
2121
const result = selector.selectInputs(changeAddress, utxos, outputs, feeRate);
2222

@@ -27,15 +27,15 @@ describe('GreedyInputSelector', () => {
2727
});
2828

2929
it('returns undefined if inputs are insufficient', () => {
30-
const utxos = [createUtXO(BigInt(1000))];
30+
const utxos = [createUTxO(BigInt(1000))];
3131
const outputs = [{ address: 'address1', value: BigInt(2000) }];
3232
const result = selector.selectInputs(changeAddress, utxos, outputs, feeRate);
3333

3434
expect(result).toBeUndefined();
3535
});
3636

3737
it('includes all outputs and correct change when no dust', () => {
38-
const utxos = [createUtXO(BigInt(10_000))];
38+
const utxos = [createUTxO(BigInt(10_000))];
3939
const outputs = [
4040
{ address: 'address1', value: BigInt(3000) },
4141
{ address: 'address2', value: BigInt(2000) }
@@ -49,7 +49,7 @@ describe('GreedyInputSelector', () => {
4949
});
5050

5151
it('adds dust change to fee instead of outputting it', () => {
52-
const utxos = [createUtXO(BigInt(6000))];
52+
const utxos = [createUTxO(BigInt(6000))];
5353
const outputs = [{ address: 'address1', value: BigInt(5500) }];
5454
const result = selector.selectInputs(changeAddress, utxos, outputs, feeRate);
5555

@@ -60,8 +60,8 @@ describe('GreedyInputSelector', () => {
6060
expect(sumInputs - sumOutputs - result!.fee).toBe(0);
6161
});
6262

63-
it('rescues dust by including an extra UtXO when it is economical', () => {
64-
const utxos = [createUtXO(BigInt(6000), 'utxo1'), createUtXO(BigInt(1000), 'utxo2')];
63+
it('rescues dust by including an extra UTxO when it is economical', () => {
64+
const utxos = [createUTxO(BigInt(6000), 'utxo1'), createUTxO(BigInt(1000), 'utxo2')];
6565
const outputs = [{ address: 'dest1', value: BigInt(5500) }];
6666

6767
const result = selector.selectInputs(changeAddress, utxos, outputs, feeRate);
@@ -78,16 +78,16 @@ describe('GreedyInputSelector', () => {
7878

7979
it('does NOT rescue dust when the extra input would cost more than the value rescued', () => {
8080
const bigFeeRate = 5;
81-
// Single UtXO leaves ~290 sats dust (below dust threshold).
82-
// Adding the second UtXO would cost ~340 sats in extra fee (68vB * 5) but
81+
// Single UTxO leaves ~290 sats dust (below dust threshold).
82+
// Adding the second UTxO would cost ~340 sats in extra fee (68vB * 5) but
8383
// only rescue ~260 sats, so it should NOT be added.
84-
const utxos = [createUtXO(BigInt(9000), 'big_utxo'), createUtXO(BigInt(600), 'small_utxo')];
84+
const utxos = [createUTxO(BigInt(9000), 'big_utxo'), createUTxO(BigInt(600), 'small_utxo')];
8585
const outputs = [{ address: 'dest1', value: BigInt(8000) }];
8686

8787
const result = selector.selectInputs(changeAddress, utxos, outputs, bigFeeRate);
8888

8989
expect(result).toBeDefined();
90-
// Only the first (large) UtXO should be used.
90+
// Only the first (large) UTxO should be used.
9191
expect(result!.selectedUTxOs.length).toBe(1);
9292
expect(result!.selectedUTxOs[0].txId).toBe('big_utxo');
9393

0 commit comments

Comments
 (0)