Remove Option trickery from TxBuilder API

see: https://github.com/bitcoindevkit/bdk/pull/258#issuecomment-754685962
This commit is contained in:
LLFourn 2021-01-11 14:14:14 +11:00
parent 735db02850
commit 890d6191a1
No known key found for this signature in database
GPG Key ID: A27093B54DA11F65
6 changed files with 455 additions and 546 deletions

View File

@ -108,12 +108,15 @@ fn main() -> Result<(), bdk::Error> {
wallet.sync(noop_progress(), None)?; wallet.sync(noop_progress(), None)?;
let send_to = wallet.get_new_address()?; let send_to = wallet.get_new_address()?;
let (psbt, details) = wallet.build_tx() let (psbt, details) = {
.add_recipient(send_to.script_pubkey(), 50_000) let mut builder = wallet.build_tx();
.enable_rbf() builder
.do_not_spend_change() .add_recipient(send_to.script_pubkey(), 50_000)
.fee_rate(FeeRate::from_sat_per_vb(5.0)) .enable_rbf()
.finish()?; .do_not_spend_change()
.fee_rate(FeeRate::from_sat_per_vb(5.0));
builder.finish()?
};
println!("Transaction details: {:#?}", details); println!("Transaction details: {:#?}", details);
println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt)));

View File

@ -156,10 +156,8 @@ mod test {
wallet.add_address_validator(Arc::new(TestValidator)); wallet.add_address_validator(Arc::new(TestValidator));
let addr = testutils!(@external descriptors, 10); let addr = testutils!(@external descriptors, 10);
wallet let mut builder = wallet.build_tx();
.build_tx() builder.add_recipient(addr.script_pubkey(), 25_000);
.add_recipient(addr.script_pubkey(), 25_000) builder.finish().unwrap();
.finish()
.unwrap();
} }
} }

View File

@ -85,9 +85,12 @@
//! // create wallet, sync, ... //! // create wallet, sync, ...
//! //!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); //! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! let (psbt, details) = wallet.build_tx().coin_selection(AlwaysSpendEverything) //! let (psbt, details) = {
//! .add_recipient(to_address.script_pubkey(), 50_000) //! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
//! .finish()?; //! builder
//! .add_recipient(to_address.script_pubkey(), 50_000);
//! builder.finish()?
//! };
//! //!
//! // inspect, sign, broadcast, ... //! // inspect, sign, broadcast, ...
//! //!

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@
//! // create a TxBuilder from a wallet //! // create a TxBuilder from a wallet
//! let mut tx_builder = wallet.build_tx(); //! let mut tx_builder = wallet.build_tx();
//! //!
//! let (psbt, tx_details) = tx_builder //! tx_builder
//! // Create a transaction with one output to `to_address` of 50_000 satoshi //! // Create a transaction with one output to `to_address` of 50_000 satoshi
//! .add_recipient(to_address.script_pubkey(), 50_000) //! .add_recipient(to_address.script_pubkey(), 50_000)
//! // With a custom fee rate of 5.0 satoshi/vbyte //! // With a custom fee rate of 5.0 satoshi/vbyte
@ -44,9 +44,8 @@
//! // Only spend non-change outputs //! // Only spend non-change outputs
//! .do_not_spend_change() //! .do_not_spend_change()
//! // Turn on RBF signaling //! // Turn on RBF signaling
//! .enable_rbf() //! .enable_rbf();
//! .finish()?; //! let (psbt, tx_details) = tx_builder.finish()?;
//!
//! # Ok::<(), bdk::Error>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
@ -80,11 +79,12 @@ impl TxBuilderContext for BumpFee {}
/// A transaction builder /// A transaction builder
/// ///
/// A `TxBuilder` is initially created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. /// A `TxBuilder` is created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. After
/// From there you set sepcific options on the builder until finally calling [`finish`] to get the transaction. /// assigning it, you set options on it until finally calling [`finish`] to consume the builder and
/// generate the transaction.
/// ///
/// Each method on TxBuilder takes and returns `&mut self` so you can use either use a chaining call /// Each option setting method on `TxBuilder` takes and returns `&mut self` so you can chain calls
/// or assign the builder and call normally as in the following example: /// as in the following example:
/// ///
/// ``` /// ```
/// # use bdk::*; /// # use bdk::*;
@ -95,27 +95,31 @@ impl TxBuilderContext for BumpFee {}
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let addr2 = addr1.clone(); /// # let addr2 = addr1.clone();
/// // chaining /// // chaining
/// let (psbt1, details) = wallet.build_tx() /// let (psbt1, details) = {
/// let mut builder = wallet.build_tx();
/// builder
/// .ordering(TxOrdering::Untouched) /// .ordering(TxOrdering::Untouched)
/// .add_recipient(addr1.script_pubkey(), 50_000) /// .add_recipient(addr1.script_pubkey(), 50_000)
/// .add_recipient(addr2.script_pubkey(), 50_000) /// .add_recipient(addr2.script_pubkey(), 50_000);
/// .finish()?; /// builder.finish()?
/// };
/// ///
/// // non-chaining /// // non-chaining
/// let mut builder = wallet.build_tx(); /// let (psbt2, details) = {
/// for addr in &[addr1, addr2] { /// let mut builder = wallet.build_tx();
/// builder.add_recipient(addr.script_pubkey(), 50_000); /// builder.ordering(TxOrdering::Untouched);
/// } /// for addr in &[addr1, addr2] {
/// let (psbt2, details) = builder.ordering(TxOrdering::Untouched).finish()?; /// builder.add_recipient(addr.script_pubkey(), 50_000);
/// // /// }
/// builder.finish()?
/// };
///
/// assert_eq!(psbt1.global.unsigned_tx.output[..2], psbt2.global.unsigned_tx.output[..2]); /// assert_eq!(psbt1.global.unsigned_tx.output[..2], psbt2.global.unsigned_tx.output[..2]);
/// # Ok::<(), bdk::Error>(()) /// # Ok::<(), bdk::Error>(())
/// ``` /// ```
/// ///
/// At the moment [`coin_selection`] is an exception - it consumes `self`. /// At the moment [`coin_selection`] is an exception to the rule as it consumes `self`.
/// This means it is usually best to call [`coin_selection`] first before calling other methods. /// This means it is usually best to call [`coin_selection`] on the return value of `build_tx` before assigning it.
///
/// Note that calling methods on the builder after calling [`finish`] will result in a panic.
/// ///
/// For further examples see [this module](super::tx_builder)'s documentation; /// For further examples see [this module](super::tx_builder)'s documentation;
/// ///
@ -128,8 +132,8 @@ pub struct TxBuilder<'a, B, D, Cs, Ctx> {
// params and coin_selection are Options not becasue they are optionally set (they are always // params and coin_selection are Options not becasue they are optionally set (they are always
// there) but because `.finish()` uses `Option::take` to get an owned value from a &mut self. // there) but because `.finish()` uses `Option::take` to get an owned value from a &mut self.
// They are only `None` after `.finish()` is called. // They are only `None` after `.finish()` is called.
pub(crate) params: Option<TxParams>, pub(crate) params: TxParams,
pub(crate) coin_selection: Option<Cs>, pub(crate) coin_selection: Cs,
pub(crate) phantom: PhantomData<Ctx>, pub(crate) phantom: PhantomData<Ctx>,
} }
@ -180,20 +184,15 @@ impl std::default::Default for FeePolicy {
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
TxBuilder<'a, B, D, Cs, Ctx> TxBuilder<'a, B, D, Cs, Ctx>
{ {
fn params(&mut self) -> &mut TxParams {
self.params
.as_mut()
.expect("method called on transaction builder after it was finalized")
}
/// Set a custom fee rate /// Set a custom fee rate
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
self.params().fee_policy = Some(FeePolicy::FeeRate(fee_rate)); self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate));
self self
} }
/// Set an absolute fee /// Set an absolute fee
pub fn fee_absolute(&mut self, fee_amount: u64) -> &mut Self { pub fn fee_absolute(&mut self, fee_amount: u64) -> &mut Self {
self.params().fee_policy = Some(FeePolicy::FeeAmount(fee_amount)); self.params.fee_policy = Some(FeePolicy::FeeAmount(fee_amount));
self self
} }
@ -261,8 +260,8 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
keychain: KeychainKind, keychain: KeychainKind,
) -> &mut Self { ) -> &mut Self {
let to_update = match keychain { let to_update = match keychain {
KeychainKind::Internal => &mut self.params().internal_policy_path, KeychainKind::Internal => &mut self.params.internal_policy_path,
KeychainKind::External => &mut self.params().external_policy_path, KeychainKind::External => &mut self.params.external_policy_path,
}; };
*to_update = Some(policy_path); *to_update = Some(policy_path);
@ -274,12 +273,12 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in /// 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. /// the "utxos" and the "unspendable" list, it will be spent.
pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, Error> { pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, Error> {
if self.params().utxos.get(&outpoint).is_none() { if self.params.utxos.get(&outpoint).is_none() {
let deriv_ctx = crate::wallet::descriptor_to_pk_ctx(self.wallet.secp_ctx()); let deriv_ctx = crate::wallet::descriptor_to_pk_ctx(self.wallet.secp_ctx());
let utxo = self.wallet.get_utxo(outpoint)?.ok_or(Error::UnknownUTXO)?; let utxo = self.wallet.get_utxo(outpoint)?.ok_or(Error::UnknownUTXO)?;
let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
let satisfaction_weight = descriptor.max_satisfaction_weight(deriv_ctx).unwrap(); let satisfaction_weight = descriptor.max_satisfaction_weight(deriv_ctx).unwrap();
self.params() self.params
.utxos .utxos
.insert(outpoint, (utxo, satisfaction_weight)); .insert(outpoint, (utxo, satisfaction_weight));
} }
@ -293,7 +292,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// ///
/// [`add_utxo`]: Self::add_utxo /// [`add_utxo`]: Self::add_utxo
pub fn manually_selected_only(&mut self) -> &mut Self { pub fn manually_selected_only(&mut self) -> &mut Self {
self.params().manually_selected_only = true; self.params.manually_selected_only = true;
self self
} }
@ -302,7 +301,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`] /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`]
/// have priority over these. See the docs of the two linked methods for more details. /// have priority over these. See the docs of the two linked methods for more details.
pub fn unspendable(&mut self, unspendable: Vec<OutPoint>) -> &mut Self { pub fn unspendable(&mut self, unspendable: Vec<OutPoint>) -> &mut Self {
self.params().unspendable = unspendable.into_iter().collect(); self.params.unspendable = unspendable.into_iter().collect();
self self
} }
@ -311,7 +310,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`] /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`]
/// have priority over this. See the docs of the two linked methods for more details. /// have priority over this. See the docs of the two linked methods for more details.
pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self { pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self {
self.params().unspendable.insert(unspendable); self.params.unspendable.insert(unspendable);
self self
} }
@ -319,13 +318,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// ///
/// **Use this option very carefully** /// **Use this option very carefully**
pub fn sighash(&mut self, sighash: SigHashType) -> &mut Self { pub fn sighash(&mut self, sighash: SigHashType) -> &mut Self {
self.params().sighash = Some(sighash); self.params.sighash = Some(sighash);
self self
} }
/// Choose the ordering for inputs and outputs of the transaction /// Choose the ordering for inputs and outputs of the transaction
pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self { pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self {
self.params().ordering = ordering; self.params.ordering = ordering;
self self
} }
@ -333,7 +332,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// ///
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator. /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
pub fn nlocktime(&mut self, locktime: u32) -> &mut Self { pub fn nlocktime(&mut self, locktime: u32) -> &mut Self {
self.params().locktime = Some(locktime); self.params.locktime = Some(locktime);
self self
} }
@ -342,7 +341,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// The `version` should always be greater than `0` and greater than `1` if the wallet's /// The `version` should always be greater than `0` and greater than `1` if the wallet's
/// descriptors contain an "older" (OP_CSV) operator. /// descriptors contain an "older" (OP_CSV) operator.
pub fn version(&mut self, version: i32) -> &mut Self { pub fn version(&mut self, version: i32) -> &mut Self {
self.params().version = Some(Version(version)); self.params.version = Some(Version(version));
self self
} }
@ -351,7 +350,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// This effectively adds all the change outputs to the "unspendable" list. See /// This effectively adds all the change outputs to the "unspendable" list. See
/// [`TxBuilder::unspendable`]. /// [`TxBuilder::unspendable`].
pub fn do_not_spend_change(&mut self) -> &mut Self { pub fn do_not_spend_change(&mut self) -> &mut Self {
self.params().change_policy = ChangeSpendPolicy::ChangeForbidden; self.params.change_policy = ChangeSpendPolicy::ChangeForbidden;
self self
} }
@ -360,14 +359,14 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// This effectively adds all the non-change outputs to the "unspendable" list. See /// This effectively adds all the non-change outputs to the "unspendable" list. See
/// [`TxBuilder::unspendable`]. /// [`TxBuilder::unspendable`].
pub fn only_spend_change(&mut self) -> &mut Self { pub fn only_spend_change(&mut self) -> &mut Self {
self.params().change_policy = ChangeSpendPolicy::OnlyChange; self.params.change_policy = ChangeSpendPolicy::OnlyChange;
self self
} }
/// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
/// [`TxBuilder::only_spend_change`] for some shortcuts. /// [`TxBuilder::only_spend_change`] for some shortcuts.
pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self { pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self {
self.params().change_policy = change_policy; self.params.change_policy = change_policy;
self self
} }
@ -376,7 +375,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// ///
/// This is useful for signers which always require it, like Trezor hardware wallets. /// This is useful for signers which always require it, like Trezor hardware wallets.
pub fn force_non_witness_utxo(&mut self) -> &mut Self { pub fn force_non_witness_utxo(&mut self) -> &mut Self {
self.params().force_non_witness_utxo = true; self.params.force_non_witness_utxo = true;
self self
} }
@ -385,7 +384,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// ///
/// This is useful for signers which always require it, like ColdCard hardware wallets. /// This is useful for signers which always require it, like ColdCard hardware wallets.
pub fn include_output_redeem_witness_script(&mut self) -> &mut Self { pub fn include_output_redeem_witness_script(&mut self) -> &mut Self {
self.params().include_output_redeem_witness_script = true; self.params.include_output_redeem_witness_script = true;
self self
} }
@ -395,13 +394,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// This is useful for offline signers that take part to a multisig. Some hardware wallets like /// This is useful for offline signers that take part to a multisig. Some hardware wallets like
/// BitBox and ColdCard are known to require this. /// BitBox and ColdCard are known to require this.
pub fn add_global_xpubs(&mut self) -> &mut Self { pub fn add_global_xpubs(&mut self) -> &mut Self {
self.params().add_global_xpubs = true; self.params.add_global_xpubs = true;
self self
} }
/// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and the change policy. /// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and the change policy.
pub fn drain_wallet(&mut self) -> &mut Self { pub fn drain_wallet(&mut self) -> &mut Self {
self.params().drain_wallet = true; self.params.drain_wallet = true;
self self
} }
@ -414,14 +413,10 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
self, self,
coin_selection: P, coin_selection: P,
) -> TxBuilder<'a, B, D, P, Ctx> { ) -> TxBuilder<'a, B, D, P, Ctx> {
assert!(
self.coin_selection.is_some(),
"can't set coin_selection after finish() has been called"
);
TxBuilder { TxBuilder {
wallet: self.wallet, wallet: self.wallet,
params: self.params, params: self.params,
coin_selection: Some(coin_selection), coin_selection,
phantom: PhantomData, phantom: PhantomData,
} }
} }
@ -431,24 +426,21 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// Returns the [`BIP174`] "PSBT" and summary details about the transaction. /// Returns the [`BIP174`] "PSBT" and summary details about the transaction.
/// ///
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
pub fn finish(&mut self) -> Result<(PSBT, TransactionDetails), Error> { pub fn finish(self) -> Result<(PSBT, TransactionDetails), Error> {
self.wallet.create_tx( self.wallet.create_tx(self.coin_selection, self.params)
self.coin_selection.take().unwrap(),
self.params.take().unwrap(),
)
} }
} }
impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> { impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list /// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self { pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
self.params().recipients = recipients; self.params.recipients = recipients;
self self
} }
/// Add a recipient to the internal list /// Add a recipient to the internal list
pub fn add_recipient(&mut self, script_pubkey: Script, amount: u64) -> &mut Self { pub fn add_recipient(&mut self, script_pubkey: Script, amount: u64) -> &mut Self {
self.params().recipients.push((script_pubkey, amount)); self.params.recipients.push((script_pubkey, amount));
self self
} }
@ -467,8 +459,8 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
/// add [`maintain_single_recipient`](Self::maintain_single_recipient) to correctly update the /// add [`maintain_single_recipient`](Self::maintain_single_recipient) to correctly update the
/// single output instead of adding one more for the change. /// single output instead of adding one more for the change.
pub fn set_single_recipient(&mut self, recipient: Script) -> &mut Self { pub fn set_single_recipient(&mut self, recipient: Script) -> &mut Self {
self.params().single_recipient = Some(recipient); self.params.single_recipient = Some(recipient);
self.params().recipients.clear(); self.params.recipients.clear();
self self
} }
@ -477,7 +469,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
/// ///
/// This will use the default nSequence value of `0xFFFFFFFD`. /// This will use the default nSequence value of `0xFFFFFFFD`.
pub fn enable_rbf(&mut self) -> &mut Self { pub fn enable_rbf(&mut self) -> &mut Self {
self.params().rbf = Some(RBFValue::Default); self.params.rbf = Some(RBFValue::Default);
self self
} }
@ -489,7 +481,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not /// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF. /// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self { pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
self.params().rbf = Some(RBFValue::Value(nsequence)); self.params.rbf = Some(RBFValue::Value(nsequence));
self self
} }
} }
@ -509,9 +501,9 @@ impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm,
/// ///
/// [`add_utxo`]: Self::add_utxo /// [`add_utxo`]: Self::add_utxo
pub fn maintain_single_recipient(&mut self) -> &mut Self { pub fn maintain_single_recipient(&mut self) -> &mut Self {
let mut recipients = self.params().recipients.drain(..).collect::<Vec<_>>(); let mut recipients = self.params.recipients.drain(..).collect::<Vec<_>>();
assert_eq!(recipients.len(), 1, "maintain_single_recipient must not be called while bumping a transactions with more than one output"); assert_eq!(recipients.len(), 1, "maintain_single_recipient must not be called while bumping a transactions with more than one output");
self.params().single_recipient = Some(recipients.pop().unwrap().0); self.params.single_recipient = Some(recipients.pop().unwrap().0);
// Since we are fee bumping and maintaining a single recipient we never want to add any more non-manual inputs. // Since we are fee bumping and maintaining a single recipient we never want to add any more non-manual inputs.
self self
} }

View File

@ -307,7 +307,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey(), 25_000).finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
@ -334,7 +336,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey(), 25_000).finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap(); let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
@ -373,7 +377,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut total_sent = 0; let mut total_sent = 0;
for _ in 0..5 { for _ in 0..5 {
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey(), 5_000).finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 5_000);
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@ -405,7 +411,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf().finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@ -413,7 +421,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fees - 5_000); assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fees - 5_000);
assert_eq!(wallet.get_balance().unwrap(), details.received); assert_eq!(wallet.get_balance().unwrap(), details.received);
let (new_psbt, new_details) = wallet.build_fee_bump(details.txid).unwrap().fee_rate(FeeRate::from_sat_per_vb(2.1)).finish().unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
let (new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
@ -437,7 +447,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000); assert_eq!(wallet.get_balance().unwrap(), 50_000);
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf().finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@ -445,7 +457,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fees); assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fees);
assert_eq!(wallet.get_balance().unwrap(), details.received); assert_eq!(wallet.get_balance().unwrap(), details.received);
let (new_psbt, new_details) = wallet.build_fee_bump(details.txid).unwrap().fee_rate(FeeRate::from_sat_per_vb(5.0)).finish().unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(5.0));
let (new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
@ -469,7 +483,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000); assert_eq!(wallet.get_balance().unwrap(), 75_000);
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf().finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@ -477,7 +493,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees); assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees);
assert_eq!(details.received, 1_000 - details.fees); assert_eq!(details.received, 1_000 - details.fees);
let (new_psbt, new_details) = wallet.build_fee_bump(details.txid).unwrap().fee_rate(FeeRate::from_sat_per_vb(10.0)).finish().unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
let (new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
@ -499,7 +517,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000); assert_eq!(wallet.get_balance().unwrap(), 75_000);
let (psbt, details) = wallet.build_tx().add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf().finish().unwrap(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@ -507,7 +527,9 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees); assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fees);
assert_eq!(details.received, 1_000 - details.fees); assert_eq!(details.received, 1_000 - details.fees);
let (new_psbt, new_details) = wallet.build_fee_bump(details.txid).unwrap().fee_rate(FeeRate::from_sat_per_vb(123.0)).finish().unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
let (new_psbt, new_details) = builder.finish().unwrap();
println!("{:#?}", new_details); println!("{:#?}", new_details);
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap();