1
1
# BDK Coin Selection
2
2
3
- ` bdk_coin_select ` is a tool to help you select inputs for making Bitcoin (ticker: BTC) transactions.
4
- It's got zero dependencies so you can paste it into your project without concern.
3
+ ` bdk_coin_select ` is a zero-dependency tool to help you select inputs for making Bitcoin (ticker: BTC) transactions.
5
4
6
5
> ⚠ This work is only ready to use by those who expect (potentially catastrophic) bugs and will have
7
6
> the time to investigate them and contribute back to this crate.
8
7
9
- ## Constructing the ` CoinSelector `
10
-
11
- The main structure is [ ` CoinSelector ` ] ( crate::CoinSelector ) . To construct it, we specify a list of
12
- candidate UTXOs and a transaction ` base_weight ` . The ` base_weight ` includes the recipient outputs
13
- and mandatory inputs (if any).
8
+ ## Synopis
14
9
15
10
``` rust
16
11
use std :: str :: FromStr ;
17
- use bdk_coin_select :: { CoinSelector , Candidate , TR_KEYSPEND_TXIN_WEIGHT };
12
+ use bdk_coin_select :: { CoinSelector , Candidate , TR_KEYSPEND_TXIN_WEIGHT , Drain , FeeRate , Target , ChangePolicy , TargetOutputs , TargetFee , DrainWeights };
18
13
use bitcoin :: { Address , Network , Transaction , TxIn , TxOut };
19
14
20
- // The address where we want to send our coins.
21
15
let recipient_addr =
22
16
Address :: from_str (" tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" ). unwrap ();
23
17
18
+ let outputs = vec! [TxOut {
19
+ value : 3_500_000 ,
20
+ script_pubkey : recipient_addr . payload. script_pubkey (),
21
+ }];
22
+
23
+ let target = Target {
24
+ outputs : TargetOutputs :: fund_outputs (outputs . iter (). map (| output | (output . weight () as u32 , output . value))),
25
+ fee : TargetFee :: from_feerate (FeeRate :: from_sat_per_vb (42.0 ))
26
+ };
27
+
24
28
let candidates = vec! [
25
29
Candidate {
26
30
// How many inputs does this candidate represents. Needed so we can
27
31
// figure out the weight of the varint that encodes the number of inputs
28
32
input_count : 1 ,
29
33
// the value of the input
30
34
value : 1_000_000 ,
31
- // the total weight of the input(s).
35
+ // the total weight of the input(s) including their witness/scriptSig
32
36
// you may need to use miniscript to figure out the correct value here.
33
- weight : TR_KEYSPEND_TXIN_WEIGHT ,
37
+ weight : TR_KEYSPEND_TXIN_WEIGHT ,
34
38
// wether it's a segwit input. Needed so we know whether to include the
35
39
// segwit header in total weight calculations.
36
40
is_segwit : true
@@ -45,91 +49,54 @@ let candidates = vec![
45
49
}
46
50
];
47
51
48
- let base_tx = Transaction {
49
- input : vec! [],
50
- // include your recipient outputs here
51
- output : vec! [TxOut {
52
- value : 900_000 ,
53
- script_pubkey : recipient_addr . payload. script_pubkey (),
54
- }],
55
- lock_time : bitcoin :: absolute :: LockTime :: from_height (0 ). unwrap (),
56
- version : 0x02 ,
57
- };
58
- let base_weight = base_tx . weight (). to_wu () as u32 ;
59
- println! (" base weight: {}" , base_weight );
60
-
61
52
// You can now select coins!
62
- let mut coin_selector = CoinSelector :: new (& candidates , base_weight );
53
+ let mut coin_selector = CoinSelector :: new (& candidates );
63
54
coin_selector . select (0 );
55
+
56
+ assert! (! coin_selector . is_target_met (target ), " we didn't select enough" );
57
+ println! (" we didn't select enough yet we're missing: {}" , coin_selector . missing (target ));
58
+ coin_selector . select (1 );
59
+ assert! (coin_selector . is_target_met (target ), " we should have enough now" );
60
+
61
+ // Now we need to know if we need a change output to drain the excess if we overshot too much
62
+ //
63
+ // We don't need to know exactly which change output we're going to use yet but we assume it's a taproot output
64
+ // that we'll use a keyspend to spend from.
65
+ let drain_weights = DrainWeights :: TR_KEYSPEND ;
66
+ // Our policy is to only add a change output if the value is over 1_000 sats
67
+ let change_policy = ChangePolicy :: min_value (drain_weights , 1_000 );
68
+ let change = coin_selector . drain (target , change_policy );
69
+ if change . is_some () {
70
+ println! (" We need to add our change output to the transaction with {} value" , change . value);
71
+ } else {
72
+ println! (" Yay we don't need to add a change output" );
73
+ }
64
74
```
65
75
66
- ## Change Policy
76
+ ## Automatic selection with Branch and Bound
67
77
68
- A change policy determines whether the drain output(s) should be in the final solution. The
69
- determination is simple: if the excess value is above a threshold then the drain should be added. To
70
- construct a change policy you always provide ` DrainWeights ` which tell the coin selector the weight
71
- cost of adding the drain. ` DrainWeights ` includes two weights. One is the weight of the drain
72
- output(s). The other is the weight of spending the drain output later on (the input weight).
78
+ You can use methods such as [ ` CoinSelector::select ` ] to manually select coins, or methods such as
79
+ [ ` CoinSelector::select_until_target_met ` ] for a rudimentary automatic selection. Probably you want
80
+ to use [ ` CoinSelector::run_bnb ` ] to do this in a smart way.
73
81
82
+ Built-in metrics are provided in the [ ` metrics ` ] submodule. Currently, only the
83
+ [ ` LowestFee ` ] ( metrics::LowestFee ) metric is considered stable. Note you * can* try and write your own
84
+ metric by implementing the [ ` BnbMetric ` ] yourself but we don't recommend this.
74
85
75
86
``` rust
76
87
use std :: str :: FromStr ;
77
- use bdk_coin_select :: {CoinSelector , Candidate , DrainWeights , TXIN_BASE_WEIGHT , ChangePolicy , TR_KEYSPEND_TXIN_WEIGHT };
78
- use bitcoin :: {Address , Network , Transaction , TxIn , TxOut };
79
- let base_tx = Transaction {
80
- input : vec! [],
81
- output : vec! [/* include your recipient outputs here */ ],
82
- lock_time : bitcoin :: absolute :: LockTime :: from_height (0 ). unwrap (),
83
- version : 0x02 ,
84
- };
85
- let base_weight = base_tx . weight (). to_wu () as u32 ;
86
-
87
- // The change output that may or may not be included in the final transaction.
88
- let drain_addr =
89
- Address :: from_str (" tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" )
90
- . expect (" address must be valid" )
91
- . require_network (Network :: Testnet )
92
- . expect (" network must match" );
93
-
94
- // The drain output(s) may or may not be included in the final tx. We calculate
95
- // the drain weight to include the output length varint weight changes from
96
- // including the drain output(s).
97
- let drain_output_weight = {
98
- let mut tx_with_drain = base_tx . clone ();
99
- tx_with_drain . output. push (TxOut {
100
- script_pubkey : drain_addr . script_pubkey (),
101
- .. Default :: default ()
102
- });
103
- tx_with_drain . weight (). to_wu () as u32 - base_weight
104
- };
105
- println! (" drain output weight: {}" , drain_output_weight );
106
-
107
- let drain_weights = DrainWeights {
108
- output_weight : drain_output_weight ,
109
- spend_weight : TR_KEYSPEND_TXIN_WEIGHT ,
110
- };
111
-
112
- // This constructs a change policy that creates change when the change value is
113
- // greater than or equal to the dust limit.
114
- let change_policy = ChangePolicy :: min_value (
115
- drain_weights ,
116
- drain_addr . script_pubkey (). dust_value (). to_sat (),
117
- );
118
- ```
119
-
120
- ## Branch and Bound
88
+ use bdk_coin_select :: { Candidate , CoinSelector , FeeRate , Target , TargetFee , TargetOutputs , ChangePolicy , TR_KEYSPEND_TXIN_WEIGHT , TR_DUST_RELAY_MIN_VALUE };
89
+ use bdk_coin_select :: metrics :: LowestFee ;
90
+ use bitcoin :: { Address , Network , Transaction , TxIn , TxOut };
121
91
122
- You can use methods such as [ ` CoinSelector::select ` ] to manually select coins, or methods such as
123
- [ ` CoinSelector::select_until_target_met ` ] for a rudimentary automatic selection. However, if you
124
- wish to automatically select coins to optimize for a given metric, [ ` CoinSelector::run_bnb ` ] can be
125
- used.
92
+ let recipient_addr =
93
+ Address :: from_str (" tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" ). unwrap ();
126
94
127
- Built-in metrics are provided in the [ ` metrics ` ] submodule. Currently, only the
128
- [ ` LowestFee ` ] ( metrics::LowestFee ) metric is considered stable.
95
+ let outputs = vec! [TxOut {
96
+ value : 210_000 ,
97
+ script_pubkey : recipient_addr . payload. script_pubkey (),
98
+ }];
129
99
130
- ``` rust
131
- use bdk_coin_select :: { Candidate , CoinSelector , FeeRate , Target , TargetFee , ChangePolicy , TR_KEYSPEND_TXIN_WEIGHT };
132
- use bdk_coin_select :: metrics :: LowestFee ;
133
100
let candidates = [
134
101
Candidate {
135
102
input_count : 1 ,
@@ -150,34 +117,35 @@ let candidates = [
150
117
is_segwit : true
151
118
}
152
119
];
153
- let base_weight = 0 ;
154
120
let drain_weights = bdk_coin_select :: DrainWeights :: default ();
155
- let dust_limit = 0 ;
121
+ // You could determine this by looking at the user's transaction history and taking an average of the feerate.
156
122
let long_term_feerate = FeeRate :: from_sat_per_vb (10.0 );
157
123
158
- let mut coin_selector = CoinSelector :: new (& candidates , base_weight );
124
+ let mut coin_selector = CoinSelector :: new (& candidates );
159
125
160
126
let target = Target {
161
127
fee : TargetFee :: from_feerate (FeeRate :: from_sat_per_vb (15.0 )),
162
- value : 210_000 ,
128
+ outputs : TargetOutputs :: fund_outputs ( outputs . iter () . map ( | output | ( output . weight () as u32 , output . value))) ,
163
129
};
164
130
131
+ // The change output must be at least this size to be relayed.
132
+ // To choose it you need to know the kind of script pubkey on your change txout.
133
+ // Here we assume it's a taproot output
134
+ let dust_limit = TR_DUST_RELAY_MIN_VALUE ;
135
+
165
136
// We use a change policy that introduces a change output if doing so reduces
166
- // the "waste" and that the change output's value is at least that of the
167
- // `dust_limit`.
137
+ // the "waste" (i.e. adding change doesn't increase the fees we'd pay if we factor in the cost to spend the output later on).
168
138
let change_policy = ChangePolicy :: min_value_and_waste (
169
139
drain_weights ,
170
140
dust_limit ,
171
141
target . fee. rate,
172
142
long_term_feerate ,
173
143
);
174
144
175
- // This metric minimizes transaction fees paid over time. The
176
- // `long_term_feerate` is used to calculate the additional fee from spending
177
- // the change output in the future.
145
+ // The LowestFee metric tries make selections that minimize your total fees paid over time.
178
146
let metric = LowestFee {
179
147
target ,
180
- long_term_feerate ,
148
+ long_term_feerate , // used to calculate the cost of spending th change output if the future
181
149
change_policy
182
150
};
183
151
@@ -203,79 +171,6 @@ match coin_selector.run_bnb(metric, 100_000) {
203
171
204
172
```
205
173
206
- ## Finalizing a Selection
207
-
208
- - [ ` is_target_met ` ] checks whether the current state of [ ` CoinSelector ` ] meets the [ ` Target ` ] .
209
- - [ ` apply_selection ` ] applies the selection to the original list of candidate ` TxOut ` s.
210
-
211
- [ `is_target_met` ] : crate::CoinSelector::is_target_met
212
- [ `apply_selection` ] : crate::CoinSelector::apply_selection
213
- [ `CoinSelector` ] : crate::CoinSelector
214
- [ `Target` ] : crate::Target
215
-
216
- ``` rust
217
- use bdk_coin_select :: {CoinSelector , Candidate , DrainWeights , Target , ChangePolicy , TR_KEYSPEND_TXIN_WEIGHT , Drain };
218
- use bitcoin :: {Amount , TxOut , Address };
219
- let base_weight = 0_u32 ;
220
- let drain_weights = DrainWeights :: TR_KEYSPEND ;
221
- use core :: str :: FromStr ;
222
-
223
- // A random target, as an example.
224
- let target = Target {
225
- value : 21_000 ,
226
- .. Default :: default ()
227
- };
228
- // Am arbitary drain policy, for the example.
229
- let change_policy = ChangePolicy :: min_value (drain_weights , 1337 );
230
-
231
- // This is a list of candidate txouts for coin selection. If a txout is picked,
232
- // our transaction's input will spend it.
233
- let candidate_txouts = vec! [
234
- TxOut {
235
- value : 100_000 ,
236
- script_pubkey : Address :: from_str (" bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr" ). unwrap (). payload. script_pubkey (),
237
- },
238
- TxOut {
239
- value : 150_000 ,
240
- script_pubkey : Address :: from_str (" bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh" ). unwrap (). payload. script_pubkey (),
241
- },
242
- TxOut {
243
- value : 200_000 ,
244
- script_pubkey : Address :: from_str (" bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8" ). unwrap (). payload. script_pubkey ()
245
- }
246
- ];
247
- // We transform the candidate txouts into something `CoinSelector` can
248
- // understand.
249
- let candidates = candidate_txouts
250
- . iter ()
251
- . map (| txout | Candidate {
252
- input_count : 1 ,
253
- value : txout . value,
254
- weight : TR_KEYSPEND_TXIN_WEIGHT , // you need to figure out the weight of the txin somehow
255
- is_segwit : txout . script_pubkey. is_witness_program (),
256
- })
257
- . collect :: <Vec <_ >>();
258
-
259
- let mut selector = CoinSelector :: new (& candidates , base_weight );
260
- selector
261
- . select_until_target_met (target )
262
- . expect (" we've got enough coins" );
263
-
264
- // Get a list of coins that are selected.
265
- let selected_coins = selector
266
- . apply_selection (& candidate_txouts )
267
- . collect :: <Vec <_ >>();
268
- assert_eq! (selected_coins . len (), 1 );
269
-
270
- // Determine whether we should add a change output.
271
- let drain = selector . drain (target , change_policy );
272
-
273
- if drain . is_some () {
274
- // add our change output to the transaction
275
- let change_value = drain . value;
276
- }
277
- ```
278
-
279
174
# Minimum Supported Rust Version (MSRV)
280
175
281
176
This library is compiles on rust v1.54 and above
0 commit comments