11# BDK Coin Selection
22
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.
54
65> ⚠ This work is only ready to use by those who expect (potentially catastrophic) bugs and will have
76> the time to investigate them and contribute back to this crate.
87
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
149
1510``` rust
1611use 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 };
1813use bitcoin :: { Address , Network , Transaction , TxIn , TxOut };
1914
20- // The address where we want to send our coins.
2115let recipient_addr =
2216 Address :: from_str (" tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" ). unwrap ();
2317
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+
2428let candidates = vec! [
2529 Candidate {
2630 // How many inputs does this candidate represents. Needed so we can
2731 // figure out the weight of the varint that encodes the number of inputs
2832 input_count : 1 ,
2933 // the value of the input
3034 value : 1_000_000 ,
31- // the total weight of the input(s).
35+ // the total weight of the input(s) including their witness/scriptSig
3236 // 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 ,
3438 // wether it's a segwit input. Needed so we know whether to include the
3539 // segwit header in total weight calculations.
3640 is_segwit : true
@@ -45,91 +49,54 @@ let candidates = vec![
4549 }
4650];
4751
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-
6152// You can now select coins!
62- let mut coin_selector = CoinSelector :: new (& candidates , base_weight );
53+ let mut coin_selector = CoinSelector :: new (& candidates );
6354coin_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+ }
6474```
6575
66- ## Change Policy
76+ ## Automatic selection with Branch and Bound
6777
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.
7381
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.
7485
7586``` rust
7687use 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 };
12191
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 ();
12694
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+ }];
12999
130- ``` rust
131- use bdk_coin_select :: { Candidate , CoinSelector , FeeRate , Target , TargetFee , ChangePolicy , TR_KEYSPEND_TXIN_WEIGHT };
132- use bdk_coin_select :: metrics :: LowestFee ;
133100let candidates = [
134101 Candidate {
135102 input_count : 1 ,
@@ -150,34 +117,35 @@ let candidates = [
150117 is_segwit : true
151118 }
152119];
153- let base_weight = 0 ;
154120let 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.
156122let long_term_feerate = FeeRate :: from_sat_per_vb (10.0 );
157123
158- let mut coin_selector = CoinSelector :: new (& candidates , base_weight );
124+ let mut coin_selector = CoinSelector :: new (& candidates );
159125
160126let target = Target {
161127 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))) ,
163129};
164130
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+
165136// 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).
168138let change_policy = ChangePolicy :: min_value_and_waste (
169139 drain_weights ,
170140 dust_limit ,
171141 target . fee. rate,
172142 long_term_feerate ,
173143);
174144
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.
178146let metric = LowestFee {
179147 target ,
180- long_term_feerate ,
148+ long_term_feerate , // used to calculate the cost of spending th change output if the future
181149 change_policy
182150};
183151
@@ -203,79 +171,6 @@ match coin_selector.run_bnb(metric, 100_000) {
203171
204172```
205173
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-
279174# Minimum Supported Rust Version (MSRV)
280175
281176This library is compiles on rust v1.54 and above
0 commit comments