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