bdkffi/tx_builder.rs
1use crate::bitcoin::{Amount, FeeRate, Input, OutPoint, Psbt, Script, Txid};
2use crate::error::{AddForeignUtxoError, CreateTxError, SighashParseError};
3use crate::types::{KeychainKind, LockTime, ScriptAmount};
4use crate::wallet::Wallet;
5
6use bdk_wallet::bitcoin::absolute::{Height as BdkHeight, LockTime as BdkLockTime};
7use bdk_wallet::bitcoin::amount::Amount as BdkAmount;
8use bdk_wallet::bitcoin::psbt::Input as BdkInput;
9use bdk_wallet::bitcoin::psbt::PsbtSighashType as BdkPsbtSighashType;
10use bdk_wallet::bitcoin::script::PushBytesBuf;
11use bdk_wallet::bitcoin::Psbt as BdkPsbt;
12use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
13use bdk_wallet::bitcoin::{OutPoint as BdkOutPoint, Sequence, Weight as BdkWeight};
14use bdk_wallet::coin_selection::{
15 CoinSelectionAlgorithm as BdkCoinSelectionAlgorithm,
16 LargestFirstCoinSelection as BdkLargestFirstCoinSelection,
17 OldestFirstCoinSelection as BdkOldestFirstCoinSelection,
18 SingleRandomDraw as BdkSingleRandomDraw,
19};
20use bdk_wallet::TxOrdering as BdkTxOrdering;
21
22use std::collections::BTreeMap;
23use std::collections::HashMap;
24use std::convert::{TryFrom, TryInto};
25use std::str::FromStr;
26use std::sync::Arc;
27
28type ChangeSpendPolicy = bdk_wallet::ChangeSpendPolicy;
29
30/// A `TxBuilder` is created by calling `build_tx` on a wallet. After assigning it, you set options on it until finally
31/// calling `finish` to consume the builder and generate the transaction.
32#[derive(Clone, uniffi::Object)]
33pub struct TxBuilder {
34 add_global_xpubs: bool,
35 recipients: Vec<(BdkScriptBuf, BdkAmount)>,
36 utxos: Vec<BdkOutPoint>,
37 unspendable: Vec<BdkOutPoint>,
38 internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
39 external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
40 change_policy: ChangeSpendPolicy,
41 manually_selected_only: bool,
42 fee_rate: Option<FeeRate>,
43 fee_absolute: Option<Arc<Amount>>,
44 drain_wallet: bool,
45 drain_to: Option<BdkScriptBuf>,
46 sequence: Option<u32>,
47 data: Vec<u8>,
48 current_height: Option<u32>,
49 locktime: Option<LockTime>,
50 allow_dust: bool,
51 version: Option<i32>,
52 sighash: Option<BdkPsbtSighashType>,
53 ordering: TxOrdering,
54 exclude_unconfirmed: bool,
55 exclude_below_confirmations: Option<u32>,
56 only_witness_utxo: bool,
57 foreign_utxos: Vec<(BdkOutPoint, BdkInput, BdkWeight, Option<u32>)>,
58 coin_selection: Option<CoinSelectionAlgorithm>,
59}
60
61#[allow(clippy::new_without_default)]
62#[uniffi::export]
63impl TxBuilder {
64 #[uniffi::constructor]
65 pub fn new() -> Self {
66 TxBuilder {
67 add_global_xpubs: false,
68 recipients: Vec::new(),
69 utxos: Vec::new(),
70 unspendable: Vec::new(),
71 internal_policy_path: None,
72 external_policy_path: None,
73 change_policy: ChangeSpendPolicy::ChangeAllowed,
74 manually_selected_only: false,
75 fee_rate: None,
76 fee_absolute: None,
77 drain_wallet: false,
78 drain_to: None,
79 sequence: None,
80 data: Vec::new(),
81 current_height: None,
82 locktime: None,
83 allow_dust: false,
84 version: None,
85 sighash: None,
86 ordering: TxOrdering::Shuffle,
87 exclude_unconfirmed: false,
88 exclude_below_confirmations: None,
89 only_witness_utxo: false,
90 foreign_utxos: Vec::new(),
91 coin_selection: None,
92 }
93 }
94
95 /// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the external and internal
96 /// descriptors.
97 ///
98 /// This is useful for offline signers that take part to a multisig. Some hardware wallets like BitBox and ColdCard
99 /// are known to require this.
100 pub fn add_global_xpubs(&self) -> Arc<Self> {
101 Arc::new(TxBuilder {
102 add_global_xpubs: true,
103 ..self.clone()
104 })
105 }
106
107 /// Exclude outpoints whose enclosing transaction is unconfirmed.
108 /// This is a shorthand for exclude_below_confirmations(1).
109 pub fn exclude_unconfirmed(&self) -> Arc<Self> {
110 Arc::new(TxBuilder {
111 exclude_unconfirmed: true,
112 ..self.clone()
113 })
114 }
115
116 /// Excludes any outpoints whose enclosing transaction has fewer than `min_confirms`
117 /// confirmations.
118 ///
119 /// `min_confirms` is the minimum number of confirmations a transaction must have in order for
120 /// its outpoints to remain spendable.
121 /// - Passing `0` will include all transactions (no filtering).
122 /// - Passing `1` will exclude all unconfirmed transactions (equivalent to
123 /// `exclude_unconfirmed`).
124 /// - Passing `6` will only allow outpoints from transactions with at least 6 confirmations.
125 ///
126 /// If you chain this with other filtering methods, the final set of unspendable outpoints will
127 /// be the union of all filters.
128 pub fn exclude_below_confirmations(&self, min_confirms: u32) -> Arc<Self> {
129 Arc::new(TxBuilder {
130 exclude_below_confirmations: Some(min_confirms),
131 ..self.clone()
132 })
133 }
134
135 /// Add a recipient to the internal list of recipients.
136 pub fn add_recipient(&self, script: &Script, amount: Arc<Amount>) -> Arc<Self> {
137 let mut recipients: Vec<(BdkScriptBuf, BdkAmount)> = self.recipients.clone();
138 recipients.append(&mut vec![(script.0.clone(), amount.0)]);
139
140 Arc::new(TxBuilder {
141 recipients,
142 ..self.clone()
143 })
144 }
145
146 /// Replace the recipients already added with a new list of recipients.
147 pub fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
148 let recipients = recipients
149 .iter()
150 .map(|script_amount| (script_amount.script.0.clone(), script_amount.amount.0)) //;
151 .collect();
152 Arc::new(TxBuilder {
153 recipients,
154 ..self.clone()
155 })
156 }
157
158 /// Add a utxo to the internal list of unspendable utxos.
159 ///
160 /// It’s important to note that the "must-be-spent" utxos added with `TxBuilder::add_utxo` have priority over this.
161 pub fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
162 let mut unspendable_vec: Vec<BdkOutPoint> = self.unspendable.clone();
163 unspendable_vec.push(unspendable.into());
164
165 Arc::new(TxBuilder {
166 unspendable: unspendable_vec,
167 ..self.clone()
168 })
169 }
170
171 /// Replace the internal list of unspendable utxos with a new list.
172 ///
173 /// It’s important to note that the "must-be-spent" utxos added with `TxBuilder::add_utxo` have priority over these.
174 pub fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
175 let new_unspendable_vec: Vec<BdkOutPoint> =
176 unspendable.into_iter().map(BdkOutPoint::from).collect();
177
178 Arc::new(TxBuilder {
179 unspendable: new_unspendable_vec,
180 ..self.clone()
181 })
182 }
183
184 /// Add a utxo to the internal list of utxos that must be spent.
185 ///
186 /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the
187 /// "unspendable" list, it will be spent.
188 pub fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
189 self.add_utxos(vec![outpoint])
190 }
191
192 /// Add the list of outpoints to the internal list of UTXOs that must be spent.
193 //
194 // If an error occurs while adding any of the UTXOs then none of them are added and the error is returned.
195 //
196 // These have priority over the “unspendable” utxos, meaning that if a utxo is present both in the “utxos” and the “unspendable” list, it will be spent.
197 pub fn add_utxos(&self, outpoints: Vec<OutPoint>) -> Arc<Self> {
198 let mut utxos: Vec<BdkOutPoint> = self.utxos.clone();
199 utxos.extend(outpoints.into_iter().map(BdkOutPoint::from));
200 Arc::new(TxBuilder {
201 utxos,
202 ..self.clone()
203 })
204 }
205
206 /// The TxBuilder::policy_path is a complex API. See the Rust docs for complete information: https://docs.rs/bdk_wallet/latest/bdk_wallet/struct.TxBuilder.html#method.policy_path
207 pub fn policy_path(
208 &self,
209 policy_path: HashMap<String, Vec<u64>>,
210 keychain: KeychainKind,
211 ) -> Arc<Self> {
212 let mut updated_self = self.clone();
213 let to_update = match keychain {
214 KeychainKind::Internal => &mut updated_self.internal_policy_path,
215 KeychainKind::External => &mut updated_self.external_policy_path,
216 };
217 *to_update = Some(
218 policy_path
219 .into_iter()
220 .map(|(key, value)| (key, value.into_iter().map(|x| x as usize).collect()))
221 .collect::<BTreeMap<String, Vec<usize>>>(),
222 );
223 Arc::new(updated_self)
224 }
225
226 /// Set a specific `ChangeSpendPolicy`. See `TxBuilder::do_not_spend_change` and `TxBuilder::only_spend_change` for
227 /// some shortcuts. This method assumes the presence of an internal keychain, otherwise it has no effect.
228 pub fn change_policy(&self, change_policy: ChangeSpendPolicy) -> Arc<Self> {
229 Arc::new(TxBuilder {
230 change_policy,
231 ..self.clone()
232 })
233 }
234
235 /// Do not spend change outputs.
236 ///
237 /// This effectively adds all the change outputs to the "unspendable" list. See `TxBuilder::unspendable`. This method
238 /// assumes the presence of an internal keychain, otherwise it has no effect.
239 pub fn do_not_spend_change(&self) -> Arc<Self> {
240 Arc::new(TxBuilder {
241 change_policy: ChangeSpendPolicy::ChangeForbidden,
242 ..self.clone()
243 })
244 }
245
246 /// Only spend change outputs.
247 ///
248 /// This effectively adds all the non-change outputs to the "unspendable" list. See `TxBuilder::unspendable`. This
249 /// method assumes the presence of an internal keychain, otherwise it has no effect.
250 pub fn only_spend_change(&self) -> Arc<Self> {
251 Arc::new(TxBuilder {
252 change_policy: ChangeSpendPolicy::OnlyChange,
253 ..self.clone()
254 })
255 }
256
257 /// Only spend utxos added by `TxBuilder::add_utxo`.
258 ///
259 /// The wallet will not add additional utxos to the transaction even if they are needed to make the transaction valid.
260 pub fn manually_selected_only(&self) -> Arc<Self> {
261 Arc::new(TxBuilder {
262 manually_selected_only: true,
263 ..self.clone()
264 })
265 }
266
267 /// Set a custom fee rate.
268 ///
269 /// This method sets the mining fee paid by the transaction as a rate on its size. This means that the total fee paid
270 /// is equal to fee_rate times the size of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core’s
271 /// default relay policy.
272 ///
273 /// Note that this is really a minimum feerate – it’s possible to overshoot it slightly since adding a change output
274 /// to drain the remaining excess might not be viable.
275 pub fn fee_rate(&self, fee_rate: &FeeRate) -> Arc<Self> {
276 Arc::new(TxBuilder {
277 fee_rate: Some(fee_rate.clone()),
278 fee_absolute: None,
279 ..self.clone()
280 })
281 }
282
283 /// Set an absolute fee The `fee_absolute` method refers to the absolute transaction fee in `Amount`. If anyone sets
284 /// both the `fee_absolute` method and the `fee_rate` method, the `FeePolicy` enum will be set by whichever method was
285 /// called last, as the `FeeRate` and `FeeAmount` are mutually exclusive.
286 ///
287 /// Note that this is really a minimum absolute fee – it’s possible to overshoot it slightly since adding a change output to drain the remaining excess might not be viable.
288 pub fn fee_absolute(&self, fee_amount: Arc<Amount>) -> Arc<Self> {
289 Arc::new(TxBuilder {
290 fee_rate: None,
291 fee_absolute: Some(fee_amount),
292 ..self.clone()
293 })
294 }
295
296 /// Spend all the available inputs. This respects filters like `TxBuilder::unspendable` and the change policy.
297 pub fn drain_wallet(&self) -> Arc<Self> {
298 Arc::new(TxBuilder {
299 drain_wallet: true,
300 ..self.clone()
301 })
302 }
303
304 /// Sets the address to drain excess coins to.
305 ///
306 /// Usually, when there are excess coins they are sent to a change address generated by the wallet. This option
307 /// replaces the usual change address with an arbitrary script_pubkey of your choosing. Just as with a change output,
308 /// if the drain output is not needed (the excess coins are too small) it will not be included in the resulting
309 /// transaction. The only difference is that it is valid to use `drain_to` without setting any ordinary recipients
310 /// with `add_recipient` (but it is perfectly fine to add recipients as well).
311 ///
312 /// If you choose not to set any recipients, you should provide the utxos that the transaction should spend via
313 /// `add_utxos`. `drain_to` is very useful for draining all the coins in a wallet with `drain_wallet` to a single
314 /// address.
315 pub fn drain_to(&self, script: &Script) -> Arc<Self> {
316 Arc::new(TxBuilder {
317 drain_to: Some(script.0.clone()),
318 ..self.clone()
319 })
320 }
321
322 /// Set an exact `nSequence` value.
323 ///
324 /// This can cause conflicts if the wallet’s descriptors contain an "older" (`OP_CSV`) operator and the given
325 /// `nsequence` is lower than the CSV value.
326 pub fn set_exact_sequence(&self, nsequence: u32) -> Arc<Self> {
327 Arc::new(TxBuilder {
328 sequence: Some(nsequence),
329 ..self.clone()
330 })
331 }
332
333 /// Add data as an output using `OP_RETURN`.
334 pub fn add_data(&self, data: Vec<u8>) -> Arc<Self> {
335 Arc::new(TxBuilder {
336 data,
337 ..self.clone()
338 })
339 }
340
341 /// Set the current blockchain height.
342 ///
343 /// This will be used to:
344 ///
345 /// 1. Set the `nLockTime` for preventing fee sniping. Note: This will be ignored if you manually specify a
346 /// `nlocktime` using `TxBuilder::nlocktime`.
347 ///
348 /// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not mature
349 /// at spending height, which is `current_height` + 1, we ignore them in the coin selection.
350 /// If you want to create a transaction that spends immature coinbase inputs,
351 /// manually add them using `TxBuilder::add_utxos`.
352 /// In both cases, if you don’t provide a current height, we use the last sync height.
353 pub fn current_height(&self, height: u32) -> Arc<Self> {
354 Arc::new(TxBuilder {
355 current_height: Some(height),
356 ..self.clone()
357 })
358 }
359
360 /// Use a specific nLockTime while creating the transaction.
361 ///
362 /// This can cause conflicts if the wallet’s descriptors contain an "after" (`OP_CLTV`) operator.
363 pub fn nlocktime(&self, locktime: LockTime) -> Arc<Self> {
364 Arc::new(TxBuilder {
365 locktime: Some(locktime),
366 ..self.clone()
367 })
368 }
369
370 /// Set whether or not the dust limit is checked.
371 ///
372 /// Note: by avoiding a dust limit check you may end up with a transaction that is non-standard.
373 pub fn allow_dust(&self, allow_dust: bool) -> Arc<Self> {
374 Arc::new(TxBuilder {
375 allow_dust,
376 ..self.clone()
377 })
378 }
379
380 /// Build a transaction with a specific version.
381 ///
382 /// The version should always be greater than 0 and greater than 1 if the wallet's descriptors contain an "older"
383 /// (`OP_CSV`) operator.
384 pub fn version(&self, version: i32) -> Arc<Self> {
385 Arc::new(TxBuilder {
386 version: Some(version),
387 ..self.clone()
388 })
389 }
390
391 /// Sign with a specific sig hash
392 ///
393 /// **Use this option very carefully**
394 pub fn sighash(&self, sighash: String) -> Result<Arc<Self>, SighashParseError> {
395 let sighash = parse_sighash_type(&sighash)?;
396 Ok(Arc::new(TxBuilder {
397 sighash: Some(sighash),
398 ..self.clone()
399 }))
400 }
401
402 /// Choose the ordering for inputs and outputs of the transaction
403 ///
404 /// When [TxBuilder::ordering] is set to [TxOrdering::Untouched], the insertion order of
405 /// recipients and manually selected UTXOs is preserved and reflected exactly in transaction's
406 /// output and input vectors respectively. If algorithmically selected UTXOs are included, they
407 /// will be placed after all the manually selected ones in the transaction's input vector.
408 pub fn ordering(&self, ordering: TxOrdering) -> Arc<Self> {
409 Arc::new(TxBuilder {
410 ordering,
411 ..self.clone()
412 })
413 }
414
415 /// Choose the coin selection algorithm
416 pub fn coin_selection(&self, coin_selection: CoinSelectionAlgorithm) -> Arc<Self> {
417 Arc::new(TxBuilder {
418 coin_selection: Some(coin_selection),
419 ..self.clone()
420 })
421 }
422
423 /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::psbt::Input::witness_utxo) field
424 /// when spending from SegWit descriptors.
425 ///
426 /// This reduces the size of the PSBT, but some signers might reject them due to the lack of
427 /// the `non_witness_utxo`.
428 pub fn only_witness_utxo(&self) -> Arc<Self> {
429 Arc::new(TxBuilder {
430 only_witness_utxo: true,
431 ..self.clone()
432 })
433 }
434
435 /// Add a foreign UTXO i.e. a UTXO not known by this wallet.
436 ///
437 /// Foreign UTXOs are not prioritized over local UTXOs. If a local UTXO is added to the
438 /// manually selected list, it will replace any conflicting foreign UTXOs. However, a foreign
439 /// UTXO cannot replace a conflicting local UTXO.
440 ///
441 /// There might be cases where the UTXO belongs to the wallet but it doesn't have knowledge of
442 /// it. This is possible if the wallet is not synced or its not being use to track
443 /// transactions. In those cases is the responsibility of the user to add any possible local
444 /// UTXOs through the [`TxBuilder::add_utxo`] method.
445 /// A manually added local UTXO will always have greater precedence than a foreign UTXO. No
446 /// matter if it was added before or after the foreign UTXO.
447 ///
448 /// At a minimum to add a foreign UTXO we need:
449 ///
450 /// 1. `outpoint`: To add it to the raw transaction.
451 /// 2. `psbt_input`: To know the value.
452 /// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the
453 /// transaction for fee calculation.
454 ///
455 /// There are several security concerns about adding foreign UTXOs that application
456 /// developers should consider. First, how do you know the value of the input is correct? If a
457 /// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies the
458 /// value by checking it against the transaction. If only a `witness_utxo` is provided then this
459 /// method doesn't verify the value but just takes it as a given -- it is up to you to check
460 /// that whoever sent you the `input_psbt` was not lying!
461 ///
462 /// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your
463 /// application it may be important that this be known precisely. If not, a malicious
464 /// counterparty may fool you into putting in a value that is too low, giving the transaction a
465 /// lower than expected feerate. They could also fool you into putting a value that is too high
466 /// causing you to pay a fee that is too high. The party who is broadcasting the transaction can
467 /// of course check the real input weight matches the expected weight prior to broadcasting.
468 ///
469 /// To guarantee the `max_weight_to_satisfy` is correct, you can require the party providing the
470 /// `psbt_input` provide a miniscript descriptor for the input so you can check it against the
471 /// `script_pubkey` and then ask it for the [`max_weight_to_satisfy`].
472 ///
473 /// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
474 ///
475 /// In order to use [`Wallet::calculate_fee`] or [`Wallet::calculate_fee_rate`] for a
476 /// transaction created with foreign UTXO(s) you must manually insert the corresponding
477 /// TxOut(s) into the tx graph using the [`Wallet::insert_txout`] function.
478 ///
479 /// # Errors
480 ///
481 /// This method returns errors in the following circumstances:
482 ///
483 /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
484 /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
485 ///
486 /// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this
487 /// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`]
488 /// is called.
489 ///
490 /// [`only_witness_utxo`]: Self::only_witness_utxo
491 /// [`finish`]: Self::finish
492 /// [`max_weight_to_satisfy`]: miniscript::Descriptor::max_weight_to_satisfy
493 pub fn add_foreign_utxo(
494 &self,
495 outpoint: OutPoint,
496 psbt_input: Input,
497 satisfaction_weight: u64,
498 ) -> Result<Arc<Self>, AddForeignUtxoError> {
499 let bdk_outpoint: BdkOutPoint = outpoint.into();
500 let bdk_input: BdkInput = psbt_input.try_into()?;
501
502 if let Some(tx) = bdk_input.non_witness_utxo.as_ref() {
503 if tx.compute_txid() != bdk_outpoint.txid {
504 return Err(AddForeignUtxoError::InvalidTxid);
505 }
506 if tx.output.len() <= bdk_outpoint.vout as usize {
507 return Err(AddForeignUtxoError::InvalidOutpoint {
508 outpoint: bdk_outpoint.to_string(),
509 });
510 }
511 } else if bdk_input.witness_utxo.is_none() {
512 return Err(AddForeignUtxoError::MissingUtxo);
513 }
514
515 let bdk_weight = BdkWeight::from_wu(satisfaction_weight);
516
517 let mut foreign_utxos = self.foreign_utxos.clone();
518 foreign_utxos.push((bdk_outpoint, bdk_input, bdk_weight, None));
519
520 Ok(Arc::new(TxBuilder {
521 foreign_utxos,
522 ..self.clone()
523 }))
524 }
525
526 /// Same as [add_foreign_utxo](TxBuilder::add_foreign_utxo) but allows to set the nSequence
527 /// value.
528 pub fn add_foreign_utxo_with_sequence(
529 &self,
530 outpoint: OutPoint,
531 psbt_input: Input,
532 satisfaction_weight: u64,
533 sequence: u32,
534 ) -> Result<Arc<Self>, AddForeignUtxoError> {
535 let bdk_outpoint: BdkOutPoint = outpoint.into();
536 let bdk_input: BdkInput = psbt_input.try_into()?;
537
538 if let Some(tx) = bdk_input.non_witness_utxo.as_ref() {
539 if tx.compute_txid() != bdk_outpoint.txid {
540 return Err(AddForeignUtxoError::InvalidTxid);
541 }
542 if tx.output.len() <= bdk_outpoint.vout as usize {
543 return Err(AddForeignUtxoError::InvalidOutpoint {
544 outpoint: bdk_outpoint.to_string(),
545 });
546 }
547 } else if bdk_input.witness_utxo.is_none() {
548 return Err(AddForeignUtxoError::MissingUtxo);
549 }
550
551 let bdk_weight = BdkWeight::from_wu(satisfaction_weight);
552
553 let mut foreign_utxos = self.foreign_utxos.clone();
554 foreign_utxos.push((bdk_outpoint, bdk_input, bdk_weight, Some(sequence)));
555
556 Ok(Arc::new(TxBuilder {
557 foreign_utxos,
558 ..self.clone()
559 }))
560 }
561
562 /// Finish building the transaction.
563 ///
564 /// Uses the thread-local random number generator (rng).
565 ///
566 /// Returns a new `Psbt` per BIP174.
567 ///
568 /// WARNING: To avoid change address reuse you must persist the changes resulting from one or more calls to this
569 /// method before closing the wallet. See `Wallet::reveal_next_address`.
570 pub fn finish(&self, wallet: &Arc<Wallet>) -> Result<Arc<Psbt>, CreateTxError> {
571 // TODO: I had to change the wallet here to be mutable. Why is that now required with the 1.0 API?
572 let mut wallet = wallet.get_wallet();
573 let tx_builder = wallet.build_tx();
574 match self.coin_selection {
575 None | Some(CoinSelectionAlgorithm::BranchAndBound) => {
576 self.finish_with_builder(tx_builder)
577 }
578 Some(CoinSelectionAlgorithm::LargestFirst) => {
579 self.finish_with_builder(tx_builder.coin_selection(BdkLargestFirstCoinSelection))
580 }
581 Some(CoinSelectionAlgorithm::OldestFirst) => {
582 self.finish_with_builder(tx_builder.coin_selection(BdkOldestFirstCoinSelection))
583 }
584 Some(CoinSelectionAlgorithm::SingleRandomDraw) => {
585 self.finish_with_builder(tx_builder.coin_selection(BdkSingleRandomDraw))
586 }
587 }
588 }
589}
590
591impl TxBuilder {
592 fn finish_with_builder<Cs>(
593 &self,
594 mut tx_builder: bdk_wallet::TxBuilder<'_, Cs>,
595 ) -> Result<Arc<Psbt>, CreateTxError>
596 where
597 Cs: BdkCoinSelectionAlgorithm,
598 {
599 if self.add_global_xpubs {
600 tx_builder.add_global_xpubs();
601 }
602 for (script, amount) in &self.recipients {
603 tx_builder.add_recipient(script.clone(), *amount);
604 }
605 if let Some(policy_path) = &self.external_policy_path {
606 tx_builder.policy_path(policy_path.clone(), KeychainKind::External);
607 }
608 if let Some(policy_path) = &self.internal_policy_path {
609 tx_builder.policy_path(policy_path.clone(), KeychainKind::Internal);
610 }
611 tx_builder.change_policy(self.change_policy);
612 if !self.utxos.is_empty() {
613 tx_builder
614 .add_utxos(&self.utxos)
615 .map_err(CreateTxError::from)?;
616 }
617 if !self.unspendable.is_empty() {
618 tx_builder.unspendable(self.unspendable.clone());
619 }
620 if self.manually_selected_only {
621 tx_builder.manually_selected_only();
622 }
623 if let Some(fee_rate) = &self.fee_rate {
624 tx_builder.fee_rate(fee_rate.0);
625 }
626 if let Some(fee_amount) = &self.fee_absolute {
627 tx_builder.fee_absolute(fee_amount.0);
628 }
629 if self.drain_wallet {
630 tx_builder.drain_wallet();
631 }
632 if let Some(script) = &self.drain_to {
633 tx_builder.drain_to(script.clone());
634 }
635 if let Some(sequence) = self.sequence {
636 tx_builder.set_exact_sequence(Sequence(sequence));
637 }
638 if !&self.data.is_empty() {
639 let push_bytes = PushBytesBuf::try_from(self.data.clone())?;
640 tx_builder.add_data(&push_bytes);
641 }
642 if let Some(height) = self.current_height {
643 let height = BdkHeight::from_consensus(height)
644 .map_err(|_| CreateTxError::LockTimeConversionError)?;
645 tx_builder.current_height(height.to_consensus_u32());
646 }
647 if let Some(locktime) = &self.locktime {
648 let bdk_locktime: BdkLockTime = locktime.try_into()?;
649 tx_builder.nlocktime(bdk_locktime);
650 }
651 if self.allow_dust {
652 tx_builder.allow_dust(self.allow_dust);
653 }
654 if let Some(version) = self.version {
655 tx_builder.version(version);
656 }
657 if let Some(sighash) = self.sighash {
658 tx_builder.sighash(sighash);
659 }
660 tx_builder.ordering(self.ordering.into());
661 if self.exclude_unconfirmed {
662 tx_builder.exclude_unconfirmed();
663 }
664 if let Some(min_confirms) = self.exclude_below_confirmations {
665 tx_builder.exclude_below_confirmations(min_confirms);
666 }
667 if self.only_witness_utxo {
668 tx_builder.only_witness_utxo();
669 }
670 for (outpoint, input, weight, sequence) in &self.foreign_utxos {
671 match sequence {
672 Some(sequence) => tx_builder
673 .add_foreign_utxo_with_sequence(
674 *outpoint,
675 input.clone(),
676 *weight,
677 Sequence(*sequence),
678 )
679 .map_err(AddForeignUtxoError::from)?,
680 None => tx_builder
681 .add_foreign_utxo(*outpoint, input.clone(), *weight)
682 .map_err(AddForeignUtxoError::from)?,
683 };
684 }
685 let psbt = tx_builder.finish().map_err(CreateTxError::from)?;
686
687 Ok(Arc::new(psbt.into()))
688 }
689}
690
691/// A `BumpFeeTxBuilder` is created by calling `build_fee_bump` on a wallet. After assigning it, you set options on it
692/// until finally calling `finish` to consume the builder and generate the transaction.
693#[derive(Clone, uniffi::Object)]
694pub struct BumpFeeTxBuilder {
695 txid: Arc<Txid>,
696 fee_rate: Arc<FeeRate>,
697 sequence: Option<u32>,
698 current_height: Option<u32>,
699 locktime: Option<LockTime>,
700 allow_dust: bool,
701 version: Option<i32>,
702 sighash: Option<BdkPsbtSighashType>,
703 ordering: TxOrdering,
704}
705
706#[uniffi::export]
707impl BumpFeeTxBuilder {
708 #[uniffi::constructor]
709 pub fn new(txid: Arc<Txid>, fee_rate: Arc<FeeRate>) -> Self {
710 BumpFeeTxBuilder {
711 txid,
712 fee_rate,
713 sequence: None,
714 current_height: None,
715 locktime: None,
716 allow_dust: false,
717 version: None,
718 sighash: None,
719 ordering: TxOrdering::Shuffle,
720 }
721 }
722
723 /// Set an exact `nSequence` value.
724 ///
725 /// This can cause conflicts if the wallet’s descriptors contain an "older" (`OP_CSV`) operator and the given
726 /// `nsequence` is lower than the CSV value.
727 pub fn set_exact_sequence(&self, nsequence: u32) -> Arc<Self> {
728 Arc::new(BumpFeeTxBuilder {
729 sequence: Some(nsequence),
730 ..self.clone()
731 })
732 }
733
734 /// Set the current blockchain height.
735 ///
736 /// This will be used to:
737 ///
738 /// 1. Set the `nLockTime` for preventing fee sniping. Note: This will be ignored if you manually specify a
739 /// `nlocktime` using `TxBuilder::nlocktime`.
740 ///
741 /// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not mature
742 /// at spending height, which is `current_height` + 1, we ignore them in the coin selection.
743 /// If you want to create a transaction that spends immature coinbase inputs,
744 /// manually add them using `TxBuilder::add_utxos`.
745 /// In both cases, if you don’t provide a current height, we use the last sync height.
746 pub fn current_height(&self, height: u32) -> Arc<Self> {
747 Arc::new(BumpFeeTxBuilder {
748 current_height: Some(height),
749 ..self.clone()
750 })
751 }
752
753 /// Use a specific nLockTime while creating the transaction.
754 ///
755 /// This can cause conflicts if the wallet’s descriptors contain an "after" (`OP_CLTV`) operator.
756 pub fn nlocktime(&self, locktime: LockTime) -> Arc<Self> {
757 Arc::new(BumpFeeTxBuilder {
758 locktime: Some(locktime),
759 ..self.clone()
760 })
761 }
762
763 /// Set whether the dust limit is checked.
764 ///
765 /// Note: by avoiding a dust limit check you may end up with a transaction that is non-standard.
766 pub fn allow_dust(&self, allow_dust: bool) -> Arc<Self> {
767 Arc::new(BumpFeeTxBuilder {
768 allow_dust,
769 ..self.clone()
770 })
771 }
772
773 /// Build a transaction with a specific version.
774 ///
775 /// The version should always be greater than 0 and greater than 1 if the wallet’s descriptors contain an "older"
776 /// (`OP_CSV`) operator.
777 pub fn version(&self, version: i32) -> Arc<Self> {
778 Arc::new(BumpFeeTxBuilder {
779 version: Some(version),
780 ..self.clone()
781 })
782 }
783
784 /// Sign with a specific sig hash
785 ///
786 /// **Use this option very carefully**
787 pub fn sighash(&self, sighash: String) -> Result<Arc<Self>, SighashParseError> {
788 let sighash = parse_sighash_type(&sighash)?;
789 Ok(Arc::new(BumpFeeTxBuilder {
790 sighash: Some(sighash),
791 ..self.clone()
792 }))
793 }
794
795 /// Choose the ordering for inputs and outputs of the transaction
796 ///
797 /// When [TxBuilder::ordering] is set to [TxOrdering::Untouched], the insertion order of
798 /// recipients and manually selected UTXOs is preserved and reflected exactly in transaction's
799 /// output and input vectors respectively. If algorithmically selected UTXOs are included, they
800 /// will be placed after all the manually selected ones in the transaction's input vector.
801 pub fn ordering(&self, ordering: TxOrdering) -> Arc<Self> {
802 Arc::new(BumpFeeTxBuilder {
803 ordering,
804 ..self.clone()
805 })
806 }
807
808 /// Finish building the transaction.
809 ///
810 /// Uses the thread-local random number generator (rng).
811 ///
812 /// Returns a new `Psbt` per BIP174.
813 ///
814 /// WARNING: To avoid change address reuse you must persist the changes resulting from one or more calls to this
815 /// method before closing the wallet. See `Wallet::reveal_next_address`.
816 pub fn finish(&self, wallet: &Arc<Wallet>) -> Result<Arc<Psbt>, CreateTxError> {
817 let mut wallet = wallet.get_wallet();
818 let mut tx_builder = wallet
819 .build_fee_bump(self.txid.0)
820 .map_err(CreateTxError::from)?;
821 tx_builder.fee_rate(self.fee_rate.0);
822 if let Some(sequence) = self.sequence {
823 tx_builder.set_exact_sequence(Sequence(sequence));
824 }
825 if let Some(height) = self.current_height {
826 let height = BdkHeight::from_consensus(height)
827 .map_err(|_| CreateTxError::LockTimeConversionError)?;
828 tx_builder.current_height(height.to_consensus_u32());
829 }
830 if let Some(locktime) = &self.locktime {
831 let bdk_locktime: BdkLockTime = locktime.try_into()?;
832 tx_builder.nlocktime(bdk_locktime);
833 }
834 if self.allow_dust {
835 tx_builder.allow_dust(self.allow_dust);
836 }
837 if let Some(version) = self.version {
838 tx_builder.version(version);
839 }
840 if let Some(sighash) = self.sighash {
841 tx_builder.sighash(sighash);
842 }
843 tx_builder.ordering(self.ordering.into());
844
845 let psbt: BdkPsbt = tx_builder.finish()?;
846
847 Ok(Arc::new(psbt.into()))
848 }
849}
850
851/// Policy regarding the use of change outputs when creating a transaction.
852#[uniffi::remote(Enum)]
853pub enum ChangeSpendPolicy {
854 /// Use both change and non-change outputs (default).
855 #[default]
856 ChangeAllowed,
857 /// Only use change outputs (see [`bdk_wallet::TxBuilder::only_spend_change`]).
858 OnlyChange,
859 /// Only use non-change outputs (see [`bdk_wallet::TxBuilder::do_not_spend_change`]).
860 ChangeForbidden,
861}
862
863/// Coin selection algorithm to use when creating a transaction.
864#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, uniffi::Enum)]
865pub enum CoinSelectionAlgorithm {
866 /// Branch and bound with single random draw fallback.
867 #[default]
868 BranchAndBound,
869 /// Select largest UTXOs first until the target is reached.
870 LargestFirst,
871 /// Select oldest local UTXOs first until the target is reached.
872 OldestFirst,
873 /// Shuffle optional UTXOs and select randomly until the target is reached.
874 SingleRandomDraw,
875}
876
877/// Ordering of the transaction's inputs and outputs.
878#[derive(Clone, Copy, Debug, Default, uniffi::Enum)]
879pub enum TxOrdering {
880 /// Randomized (default)
881 #[default]
882 Shuffle,
883 /// Untouched
884 ///
885 /// Untouched insertion order for recipients and for manually added UTXOs. This guarantees all
886 /// recipients preserve insertion order in the transaction's output vector and manually added
887 /// UTXOs preserve insertion order in the transaction's input vector, but does not make any
888 /// guarantees about algorithmically selected UTXOs. However, by design they will always be
889 /// placed after the manually selected ones.
890 Untouched,
891}
892
893impl From<TxOrdering> for BdkTxOrdering {
894 fn from(value: TxOrdering) -> Self {
895 match value {
896 TxOrdering::Shuffle => BdkTxOrdering::Shuffle,
897 TxOrdering::Untouched => BdkTxOrdering::Untouched,
898 }
899 }
900}
901
902fn parse_sighash_type(sighash: &str) -> Result<BdkPsbtSighashType, SighashParseError> {
903 BdkPsbtSighashType::from_str(sighash).map_err(|error| SighashParseError::Invalid {
904 error_message: error.to_string(),
905 })
906}
907
908#[cfg(test)]
909mod tests {
910 use super::{CoinSelectionAlgorithm, TxBuilder};
911 use crate::bitcoin::{Amount, FeeRate};
912 use std::sync::Arc;
913
914 #[test]
915 fn tx_builder_defaults_to_upstream_coin_selection() {
916 assert_eq!(TxBuilder::new().coin_selection, None);
917 }
918
919 #[test]
920 fn tx_builder_coin_selection_sets_algorithm_and_keeps_chainable_state() {
921 let tx_builder = TxBuilder::new()
922 .coin_selection(CoinSelectionAlgorithm::LargestFirst)
923 .manually_selected_only();
924
925 assert_eq!(
926 tx_builder.coin_selection,
927 Some(CoinSelectionAlgorithm::LargestFirst)
928 );
929 assert!(tx_builder.manually_selected_only);
930 }
931
932 #[test]
933 fn tx_builder_coin_selection_accepts_all_supported_algorithms() {
934 let algorithms = [
935 CoinSelectionAlgorithm::BranchAndBound,
936 CoinSelectionAlgorithm::LargestFirst,
937 CoinSelectionAlgorithm::OldestFirst,
938 CoinSelectionAlgorithm::SingleRandomDraw,
939 ];
940
941 for algorithm in algorithms {
942 let tx_builder = TxBuilder::new().coin_selection(algorithm);
943 assert_eq!(tx_builder.coin_selection, Some(algorithm));
944 }
945 }
946
947 #[test]
948 fn tx_builder_fee_absolute_overrides_fee_rate() {
949 let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
950 let tx_builder = TxBuilder::new()
951 .fee_rate(&fee_rate)
952 .fee_absolute(Arc::new(Amount::from_sat(500)));
953
954 assert!(tx_builder.fee_rate.is_none());
955 assert_eq!(
956 tx_builder.fee_absolute.as_ref().map(|fee| fee.to_sat()),
957 Some(500)
958 );
959 }
960
961 #[test]
962 fn tx_builder_fee_rate_overrides_fee_absolute() {
963 let fee_rate = FeeRate::from_sat_per_vb(2).unwrap();
964 let tx_builder = TxBuilder::new()
965 .fee_absolute(Arc::new(Amount::from_sat(500)))
966 .fee_rate(&fee_rate);
967
968 assert!(tx_builder.fee_absolute.is_none());
969 assert_eq!(
970 tx_builder
971 .fee_rate
972 .as_ref()
973 .map(FeeRate::to_sat_per_vb_ceil),
974 Some(2)
975 );
976 }
977}