pub use utils::IsDust;
use address_validator::AddressValidator;
+use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
-use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxBuilderContext};
+use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{
check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx,
DUST_LIMIT_SATOSHI,
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
/// needs to operate, like [generating addresses](Wallet::get_new_address), [returning the balance](Wallet::get_balance),
-/// [creating transactions](Wallet::create_tx), etc.
+/// [creating transactions](Wallet::build_tx), etc.
///
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
self.database.borrow().iter_utxos()
}
+ /// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
+ /// wallet's database.
+ pub fn get_utxo(&self, outpoint: OutPoint) -> Result<Option<UTXO>, Error> {
+ self.database.borrow().get_utxo(&outpoint)
+ }
+
/// Return the list of transactions made and received by the wallet
///
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
self.address_validators.push(validator);
}
- /// Create a new transaction following the options specified in the `builder`
+ /// Start building a transaction.
+ ///
+ /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
///
/// ## Example
///
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
- /// let (psbt, details) = wallet.create_tx(
- /// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
- /// )?;
+ /// let (psbt, details) = wallet.build_tx()
+ /// .add_recipient(to_address.script_pubkey(), 50_000)
+ /// .finish()?;
+ ///
/// // sign and broadcast ...
/// # Ok::<(), bdk::Error>(())
/// ```
- pub fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm<D>>(
+ ///
+ /// [`TxBuilder`]: crate::TxBuilder
+ pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
+ TxBuilder {
+ wallet: &self,
+ params: Some(TxParams::default()),
+ coin_selection: Some(DefaultCoinSelectionAlgorithm::default()),
+ phantom: core::marker::PhantomData,
+ }
+ }
+
+ pub(crate) fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm<D>>(
&self,
- builder: TxBuilder<D, Cs, CreateTx>,
+ coin_selection: Cs,
+ params: TxParams,
) -> Result<(PSBT, TransactionDetails), Error> {
let external_policy = self
.descriptor
// The policy allows spending external outputs, but it requires a policy path that hasn't been
// provided
- if builder.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange
+ if params.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange
&& external_policy.requires_path()
- && builder.external_policy_path.is_none()
+ && params.external_policy_path.is_none()
{
return Err(Error::SpendingPolicyRequired(KeychainKind::External));
};
// Same for the internal_policy path, if present
if let Some(internal_policy) = &internal_policy {
- if builder.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
+ if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
&& internal_policy.requires_path()
- && builder.internal_policy_path.is_none()
+ && params.internal_policy_path.is_none()
{
return Err(Error::SpendingPolicyRequired(KeychainKind::Internal));
};
}
let external_requirements = external_policy.get_condition(
- builder
+ params
.external_policy_path
.as_ref()
.unwrap_or(&BTreeMap::new()),
.map(|policy| {
Ok::<_, Error>(
policy.get_condition(
- builder
+ params
.internal_policy_path
.as_ref()
.unwrap_or(&BTreeMap::new()),
.merge(&internal_requirements.unwrap_or_default())?;
debug!("Policy requirements: {:?}", requirements);
- let version = match builder.version {
+ let version = match params.version {
Some(tx_builder::Version(0)) => {
return Err(Error::Generic("Invalid version `0`".into()))
}
_ => 1,
};
- let lock_time = match builder.locktime {
+ let lock_time = match params.locktime {
// No nLockTime, default to 0
None => requirements.timelock.unwrap_or(0),
// Specific nLockTime required and we have no constraints, so just set to that value
Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{}`, but at least `{}` is required to spend from this script", x, requirements.timelock.unwrap())))
};
- let n_sequence = match (builder.rbf, requirements.csv) {
+ let n_sequence = match (params.rbf, requirements.csv) {
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
(None, None) if lock_time != 0 => 0xFFFFFFFE,
// No RBF, CSV or nLockTime, make the transaction final
(Some(rbf), _) => rbf.get_value(),
};
+ let (fee_rate, mut fee_amount) = match params
+ .fee_policy
+ .as_ref()
+ .unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
+ {
+ //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256
+ FeePolicy::FeeAmount(fee) => {
+ if let Some(previous_fee) = params.bumping_fee {
+ if *fee < previous_fee.absolute {
+ return Err(Error::FeeTooLow {
+ required: previous_fee.absolute,
+ });
+ }
+ }
+ (FeeRate::from_sat_per_vb(0.0), *fee as f32)
+ }
+ FeePolicy::FeeRate(rate) => {
+ if let Some(previous_fee) = params.bumping_fee {
+ let required_feerate = FeeRate::from_sat_per_vb(previous_fee.rate + 1.0);
+ if *rate < required_feerate {
+ return Err(Error::FeeRateTooLow {
+ required: required_feerate,
+ });
+ }
+ }
+ (*rate, 0.0)
+ }
+ };
+
let mut tx = Transaction {
version,
lock_time,
output: vec![],
};
- let (fee_rate, mut fee_amount) = match builder
- .fee_policy
- .as_ref()
- .unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
- {
- FeePolicy::FeeAmount(amount) => (FeeRate::from_sat_per_vb(0.0), *amount as f32),
- FeePolicy::FeeRate(rate) => (*rate, 0.0),
- };
-
- // try not to move from `builder` because we still need to use it later.
- let recipients = match &builder.single_recipient {
+ // try not to move from `params` because we still need to use it later.
+ let recipients = match ¶ms.single_recipient {
Some(recipient) => vec![(recipient, 0)],
- None => builder.recipients.iter().map(|(r, v)| (r, *v)).collect(),
+ None => params.recipients.iter().map(|(r, v)| (r, *v)).collect(),
};
- if builder.single_recipient.is_some()
- && !builder.manually_selected_only
- && !builder.drain_wallet
+ if params.single_recipient.is_some()
+ && !params.manually_selected_only
+ && !params.drain_wallet
+ && params.bumping_fee.is_none()
{
return Err(Error::SingleRecipientNoInputs);
}
return Err(Error::NoRecipients);
}
- if builder.manually_selected_only && builder.utxos.is_empty() {
+ if params.manually_selected_only && params.utxos.is_empty() {
return Err(Error::NoUtxosSelected);
}
fee_amount += calc_fee_bytes(tx.get_weight());
for (index, (script_pubkey, satoshi)) in recipients.into_iter().enumerate() {
- let value = match builder.single_recipient {
+ let value = match params.single_recipient {
Some(_) => 0,
None if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
None => satoshi,
outgoing += value;
}
- if builder.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
+ if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
&& self.change_descriptor.is_none()
{
return Err(Error::Generic(
}
let (required_utxos, optional_utxos) = self.preselect_utxos(
- builder.change_policy,
- &builder.unspendable,
- &builder.utxos,
- builder.drain_wallet,
- builder.manually_selected_only,
- false, // we don't mind using unconfirmed outputs here, hopefully coin selection will sort this out?
+ params.change_policy,
+ ¶ms.unspendable,
+ params.utxos.values().cloned().collect(),
+ params.drain_wallet,
+ params.manually_selected_only,
+ params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
)?;
let coin_selection::CoinSelectionResult {
selected,
selected_amount,
mut fee_amount,
- } = builder.coin_selection.coin_select(
+ } = coin_selection.coin_select(
self.database.borrow().deref(),
required_utxos,
optional_utxos,
.collect();
// prepare the change output
- let change_output = match builder.single_recipient {
+ let change_output = match params.single_recipient {
Some(_) => None,
None => {
let change_script = self.get_change_address()?;
}
// sort input/outputs according to the chosen algorithm
- builder.ordering.sort_tx(&mut tx);
+ params.ordering.sort_tx(&mut tx);
let txid = tx.txid();
- let psbt = self.complete_transaction(tx, selected, builder)?;
+ let psbt = self.complete_transaction(tx, selected, params)?;
let transaction_details = TransactionDetails {
transaction: None,
Ok((psbt, transaction_details))
}
- /// Bump the fee of a transaction following the options specified in the `builder`
+ /// Bump the fee of a transaction previously created with this wallet.
///
- /// Return an error if the transaction is already confirmed or doesn't explicitly signal RBF.
+ /// Returns an error if the transaction is already confirmed or doesn't explicitly signal
+ /// *repalce by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
+ /// pre-populated with the inputs and outputs of the original transaction.
///
/// **NOTE**: if the original transaction was made with [`TxBuilder::set_single_recipient`],
/// the [`TxBuilder::maintain_single_recipient`] flag should be enabled to correctly reduce the
/// only output's value in order to increase the fees.
///
- /// If the `builder` specifies some `utxos` that must be spent, they will be added to the
- /// transaction regardless of whether they are necessary or not to cover additional fees.
- ///
/// ## Example
///
/// ```no_run
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
/// let txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
- /// let (psbt, details) = wallet.bump_fee(
- /// &txid,
- /// TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(5.0)),
- /// )?;
+ /// let (psbt, details) = wallet.build_fee_bump(txid)?
+ /// .fee_rate(FeeRate::from_sat_per_vb(5.0))
+ /// .finish()?;
/// // sign and broadcast ...
/// # Ok::<(), bdk::Error>(())
/// ```
// TODO: support for merging multiple transactions while bumping the fees
// TODO: option to force addition of an extra output? seems bad for privacy to update the
// change
- pub fn bump_fee<Cs: coin_selection::CoinSelectionAlgorithm<D>>(
+ pub fn build_fee_bump(
&self,
- txid: &Txid,
- builder: TxBuilder<D, Cs, BumpFee>,
- ) -> Result<(PSBT, TransactionDetails), Error> {
+ txid: Txid,
+ ) -> Result<TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
let mut details = match self.database.borrow().get_tx(&txid, true)? {
None => return Err(Error::TransactionNotFound),
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
return Err(Error::IrreplaceableTransaction);
}
- // the new tx must "pay for its bandwidth"
let vbytes = tx.get_weight() as f32 / 4.0;
- let required_feerate = FeeRate::from_sat_per_vb(details.fees as f32 / vbytes + 1.0);
-
- // find the index of the output that we can update. either the change or the only one if
- // it's `single_recipient`
- let updatable_output = match builder.single_recipient {
- Some(_) if tx.output.len() != 1 => return Err(Error::SingleRecipientMultipleOutputs),
- Some(_) => Some(0),
- None => {
- let mut change_output = None;
- for (index, txout) in tx.output.iter().enumerate() {
- // look for an output that we know and that has the right KeychainKind. We use
- // `get_descriptor_for` to find what's the KeychainKind for `Internal`
- // addresses really is, because if there's no change_descriptor it's actually equal
- // to "External"
- let (_, change_type) = self.get_descriptor_for_keychain(KeychainKind::Internal);
- match self
- .database
- .borrow()
- .get_path_from_script_pubkey(&txout.script_pubkey)?
- {
- Some((keychain, _)) if keychain == change_type => {
- change_output = Some(index);
- break;
- }
- _ => {}
- }
- }
-
- change_output
- }
- };
- let updatable_output = match updatable_output {
- Some(updatable_output) => updatable_output,
- None => {
- // we need a change output, add one here and take into account the extra fees for it
- let change_script = self.get_change_address()?;
- let change_txout = TxOut {
- script_pubkey: change_script,
- value: 0,
- };
- tx.output.push(change_txout);
-
- tx.output.len() - 1
- }
- };
-
- // initially always remove the output we can change
- let mut removed_updatable_output = tx.output.remove(updatable_output);
- if self.is_mine(&removed_updatable_output.script_pubkey)? {
- details.received -= removed_updatable_output.value;
- }
+ let feerate = details.fees as f32 / vbytes;
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
- let original_sequence = tx.input[0].sequence;
-
// remove the inputs from the tx and process them
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
- let mut original_utxos = original_txin
+ let original_utxos = original_txin
.iter()
- .map(|txin| -> Result<(UTXO, usize), Error> {
+ .map(|txin| -> Result<(OutPoint, (UTXO, usize)), Error> {
let txout = self
.database
.borrow()
.get_path_from_script_pubkey(&txout.script_pubkey)?
{
Some((keychain, _)) => (
- self.get_descriptor_for_keychain(keychain)
+ self._get_descriptor_for_keychain(keychain)
.0
.max_satisfaction_weight(deriv_ctx)
.unwrap(),
keychain,
};
- Ok((utxo, weight))
- })
- .collect::<Result<Vec<_>, _>>()?;
-
- if builder.manually_selected_only && builder.utxos.is_empty() {
- return Err(Error::NoUtxosSelected);
- }
-
- let builder_extra_utxos = builder
- .utxos
- .iter()
- .filter(|utxo| {
- !original_txin
- .iter()
- .any(|txin| &&txin.previous_output == utxo)
+ Ok((utxo.outpoint, (utxo, weight)))
})
- .cloned()
- .collect::<Vec<_>>();
-
- let (mut required_utxos, optional_utxos) = self.preselect_utxos(
- builder.change_policy,
- &builder.unspendable,
- &builder_extra_utxos[..],
- builder.drain_wallet,
- builder.manually_selected_only,
- true, // we only want confirmed transactions for RBF
- )?;
-
- required_utxos.append(&mut original_utxos);
+ .collect::<Result<BTreeMap<OutPoint, (UTXO, usize)>, _>>()?;
- let amount_needed = tx.output.iter().fold(0, |acc, out| acc + out.value);
- let (new_feerate, initial_fee) = match builder
- .fee_policy
- .as_ref()
- .unwrap_or(&FeePolicy::FeeRate(FeeRate::default()))
- {
- FeePolicy::FeeAmount(amount) => {
- if *amount < details.fees {
- return Err(Error::FeeTooLow {
- required: details.fees,
- });
- }
- (FeeRate::from_sat_per_vb(0.0), *amount as f32)
- }
- FeePolicy::FeeRate(rate) => {
- if *rate < required_feerate {
- return Err(Error::FeeRateTooLow {
- required: required_feerate,
- });
+ if tx.output.len() > 1 {
+ let mut change_index = None;
+ for (index, txout) in tx.output.iter().enumerate() {
+ let (_, change_type) = self._get_descriptor_for_keychain(KeychainKind::Internal);
+ match self
+ .database
+ .borrow()
+ .get_path_from_script_pubkey(&txout.script_pubkey)?
+ {
+ Some((keychain, _)) if keychain == change_type => change_index = Some(index),
+ _ => {}
}
- (*rate, tx.get_weight() as f32 / 4.0 * rate.as_sat_vb())
- }
- };
-
- let coin_selection::CoinSelectionResult {
- selected,
- selected_amount,
- fee_amount,
- } = builder.coin_selection.coin_select(
- self.database.borrow().deref(),
- required_utxos,
- optional_utxos,
- new_feerate,
- amount_needed,
- initial_fee,
- )?;
-
- tx.input = selected
- .iter()
- .map(|u| bitcoin::TxIn {
- previous_output: u.outpoint,
- script_sig: Script::default(),
- // TODO: use builder.n_sequence??
- sequence: original_sequence,
- witness: vec![],
- })
- .collect();
-
- details.sent = selected_amount;
-
- let mut fee_amount = fee_amount.ceil() as u64;
- let removed_output_fee_cost = (serialize(&removed_updatable_output).len() as f32
- * new_feerate.as_sat_vb())
- .ceil() as u64;
-
- let change_val = selected_amount - amount_needed - fee_amount;
- let change_val_after_add = change_val.saturating_sub(removed_output_fee_cost);
- match builder.single_recipient {
- None if change_val_after_add.is_dust() => {
- // skip the change output because it's dust, this adds up to the fees
- fee_amount += change_val;
- }
- Some(_) if change_val_after_add.is_dust() => {
- // single_recipient but the only output would be below dust limit
- // TODO: or OutputBelowDustLimit?
- return Err(Error::InsufficientFunds {
- needed: DUST_LIMIT_SATOSHI,
- available: change_val_after_add,
- });
- }
- None => {
- removed_updatable_output.value = change_val_after_add;
- fee_amount += removed_output_fee_cost;
- details.received += change_val_after_add;
-
- tx.output.push(removed_updatable_output);
}
- Some(_) => {
- removed_updatable_output.value = change_val_after_add;
- fee_amount += removed_output_fee_cost;
-
- // single recipient and it's our address
- if self.is_mine(&removed_updatable_output.script_pubkey)? {
- details.received = change_val_after_add;
- }
- tx.output.push(removed_updatable_output);
+ if let Some(change_index) = change_index {
+ tx.output.remove(change_index);
}
}
- // sort input/outputs according to the chosen algorithm
- builder.ordering.sort_tx(&mut tx);
-
- // TODO: check that we are not replacing more than 100 txs from mempool
-
- details.txid = tx.txid();
- details.fees = fee_amount;
- details.timestamp = time::get_timestamp();
-
- let psbt = self.complete_transaction(tx, selected, builder)?;
+ let params = TxParams {
+ // TODO: figure out what rbf option should be?
+ version: Some(tx_builder::Version(tx.version)),
+ recipients: tx
+ .output
+ .into_iter()
+ .map(|txout| (txout.script_pubkey, txout.value))
+ .collect(),
+ utxos: original_utxos,
+ bumping_fee: Some(tx_builder::PreviousFee {
+ absolute: details.fees,
+ rate: feerate,
+ }),
+ ..Default::default()
+ };
- Ok((psbt, details))
+ Ok(TxBuilder {
+ wallet: &self,
+ params: Some(params),
+ coin_selection: Some(DefaultCoinSelectionAlgorithm::default()),
+ phantom: core::marker::PhantomData,
+ })
}
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
- /// # let (psbt, _) = wallet.create_tx(TxBuilder::new())?;
+ /// # let (psbt, _) = wallet.build_tx().finish()?;
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
/// # Ok::<(), bdk::Error>(())
pub fn sign(&self, mut psbt: PSBT, assume_height: Option<u32>) -> Result<(PSBT, bool), Error> {
&self.secp
}
+ /// Returns the descriptor used to create adddresses for a particular `keychain`.
+ pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
+ let (descriptor, _) = self._get_descriptor_for_keychain(keychain);
+ descriptor
+ }
+
// Internals
- fn get_descriptor_for_keychain(
+ fn _get_descriptor_for_keychain(
&self,
keychain: KeychainKind,
) -> (&ExtendedDescriptor, KeychainKind) {
.database
.borrow()
.get_path_from_script_pubkey(&txout.script_pubkey)?
- .map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain).0, child))
+ .map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child))
.map(|(desc, child)| desc.derive(ChildNumber::from_normal_idx(child).unwrap())))
}
fn get_change_address(&self) -> Result<Script, Error> {
let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
- let (desc, keychain) = self.get_descriptor_for_keychain(KeychainKind::Internal);
+ let (desc, keychain) = self._get_descriptor_for_keychain(KeychainKind::Internal);
let index = self.fetch_and_increment_index(keychain)?;
Ok(desc
}
fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
- let (descriptor, keychain) = self.get_descriptor_for_keychain(keychain);
+ let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
let index = match descriptor.is_fixed() {
true => 0,
false => self.database.borrow_mut().increment_last_index(keychain)?,
from: u32,
mut count: u32,
) -> Result<(), Error> {
- let (descriptor, keychain) = self.get_descriptor_for_keychain(keychain);
+ let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
if descriptor.is_fixed() {
if from > 0 {
return Ok(());
(
utxo,
self.get_descriptor_for_keychain(keychain)
- .0
.max_satisfaction_weight(deriv_ctx)
.unwrap(),
)
&self,
change_policy: tx_builder::ChangeSpendPolicy,
unspendable: &HashSet<OutPoint>,
- manually_selected: &[OutPoint],
+ manually_selected: Vec<(UTXO, usize)>,
must_use_all_available: bool,
manual_only: bool,
must_only_use_confirmed_tx: bool,
// must_spend <- manually selected utxos
// may_spend <- all other available utxos
let mut may_spend = self.get_available_utxos()?;
- let mut must_spend = {
- let must_spend_idx = manually_selected
+ may_spend.retain(|may_spend| {
+ manually_selected
.iter()
- .map(|manually_selected| {
- may_spend
- .iter()
- .position(|available| available.0.outpoint == *manually_selected)
- .ok_or(Error::UnknownUTXO)
- })
- .collect::<Result<Vec<_>, _>>()?;
-
- must_spend_idx
- .into_iter()
- .map(|i| may_spend.remove(i))
- .collect()
- };
+ .find(|manually_selected| manually_selected.0.outpoint == may_spend.0.outpoint)
+ .is_none()
+ });
+ let mut must_spend = manually_selected;
// NOTE: we are intentionally ignoring `unspendable` here. i.e manual
// selection overrides unspendable.
Ok((must_spend, may_spend))
}
- fn complete_transaction<
- Cs: coin_selection::CoinSelectionAlgorithm<D>,
- Ctx: TxBuilderContext,
- >(
+ fn complete_transaction(
&self,
tx: Transaction,
selected: Vec<UTXO>,
- builder: TxBuilder<D, Cs, Ctx>,
+ params: TxParams,
) -> Result<PSBT, Error> {
use bitcoin::util::psbt::serialize::Serialize;
let mut psbt = PSBT::from_unsigned_tx(tx)?;
- if builder.add_global_xpubs {
+ if params.add_global_xpubs {
let mut all_xpubs = self.descriptor.get_extended_keys()?;
if let Some(change_descriptor) = &self.change_descriptor {
all_xpubs.extend(change_descriptor.get_extended_keys()?);
None => continue,
};
- // Only set it if the builder has a custom one, otherwise leave blank which defaults to
+ // Only set it if the params has a custom one, otherwise leave blank which defaults to
// SIGHASH_ALL
- if let Some(sighash_type) = builder.sighash {
+ if let Some(sighash_type) = params.sighash {
psbt_input.sighash_type = Some(sighash_type);
}
None => continue,
};
- let (desc, _) = self.get_descriptor_for_keychain(keychain);
+ let (desc, _) = self._get_descriptor_for_keychain(keychain);
psbt_input.hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?;
let derived_descriptor = desc.derive(ChildNumber::from_normal_idx(child)?);
psbt_input.witness_utxo =
Some(prev_tx.output[prev_output.vout as usize].clone());
}
- if !derived_descriptor.is_witness() || builder.force_non_witness_utxo {
+ if !derived_descriptor.is_witness() || params.force_non_witness_utxo {
psbt_input.non_witness_utxo = Some(prev_tx);
}
}
.borrow()
.get_path_from_script_pubkey(&tx_output.script_pubkey)?
{
- let (desc, _) = self.get_descriptor_for_keychain(keychain);
+ let (desc, _) = self._get_descriptor_for_keychain(keychain);
psbt_output.hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?;
- if builder.include_output_redeem_witness_script {
+ if params.include_output_redeem_witness_script {
let derived_descriptor = desc.derive(ChildNumber::from_normal_idx(child)?);
psbt_output.witness_script = derived_descriptor.psbt_witness_script(&self.secp);
psbt_output.redeem_script = derived_descriptor.psbt_redeem_script(&self.secp);
debug!("Found descriptor {:?}/{}", keychain, child);
// merge hd_keypaths
- let (desc, _) = self.get_descriptor_for_keychain(keychain);
+ let desc = self.get_descriptor_for_keychain(keychain);
let mut hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?;
psbt_input.hd_keypaths.append(&mut hd_keypaths);
}
#[should_panic(expected = "NoRecipients")]
fn test_create_tx_empty_recipients() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
- wallet
- .create_tx(TxBuilder::with_recipients(vec![]))
- .unwrap();
+ wallet.build_tx().finish().unwrap();
}
#[test]
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .manually_selected_only()
- .utxos(vec![]),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .manually_selected_only()
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(0))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .version(0)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(1))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .version(1)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(42))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .version(42)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.version, 42);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .nlocktime(630_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .nlocktime(630_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(50000),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .nlocktime(50000)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf()
+ .finish()
.unwrap();
-
// When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
// It will be set to the OP_CSV value, in this case 6
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .enable_rbf_with_sequence(3),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf_with_sequence(3)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .enable_rbf_with_sequence(0xFFFFFFFE),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf_with_sequence(0xFFFFFFFE)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .enable_rbf_with_sequence(0xDEADBEEF),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf_with_sequence(0xDEADBEEF)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xDEADBEEF);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .do_not_spend_change(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .do_not_spend_change()
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .fee_rate(FeeRate::from_sat_per_vb(5.0)),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .fee_rate(FeeRate::from_sat_per_vb(5.0))
+ .finish()
.unwrap();
assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::from_sat_per_vb(5.0), @add_signature);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .fee_absolute(100),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .fee_absolute(100)
+ .finish()
.unwrap();
assert_eq!(details.fees, 100);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .fee_absolute(0),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .fee_absolute(0)
+ .finish()
.unwrap();
assert_eq!(details.fees, 0);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (_psbt, _details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .fee_absolute(60_000),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .fee_absolute(60_000)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
- .ordering(TxOrdering::Untouched),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .ordering(TxOrdering::Untouched)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 2);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 49_800,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 49_800)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
let addr = wallet.get_new_address().unwrap();
// very high fee rate, so that the only output would be below dust
wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .fee_rate(FeeRate::from_sat_per_vb(453.0)),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .fee_rate(FeeRate::from_sat_per_vb(453.0))
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![
- (addr.script_pubkey(), 30_000),
- (addr.script_pubkey(), 10_000),
- ])
- .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .add_recipient(addr.script_pubkey(), 10_000)
+ .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 3);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 30_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .finish()
.unwrap();
assert_eq!(psbt.inputs[0].sighash_type, None);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
- .sighash(bitcoin::SigHashType::Single),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .sighash(bitcoin::SigHashType::Single)
+ .finish()
.unwrap();
assert_eq!(
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
let addr = testutils!(@external descriptors, 5);
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert_eq!(
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert_eq!(psbt.inputs[0].redeem_script, None);
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
let script = Script::from(
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none());
get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none());
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .force_non_witness_utxo(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .force_non_witness_utxo()
+ .finish()
.unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)]).add_utxo(
- OutPoint {
- txid: small_output_txid,
- vout: 0,
- },
- ),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .add_utxo(OutPoint {
+ txid: small_output_txid,
+ vout: 0,
+ })
+ .unwrap()
+ .finish()
.unwrap();
assert_eq!(
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
- .add_utxo(OutPoint {
- txid: small_output_txid,
- vout: 0,
- })
- .manually_selected_only(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .add_utxo(OutPoint {
+ txid: small_output_txid,
+ vout: 0,
+ })
+ .unwrap()
+ .manually_selected_only()
+ .finish()
.unwrap();
}
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 30_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .finish()
.unwrap();
}
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
- .policy_path(path, KeychainKind::External),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .policy_path(path, KeychainKind::External)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
- .policy_path(path, KeychainKind::External),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 30_000)
+ .policy_path(path, KeychainKind::External)
+ .finish()
.unwrap();
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 144);
let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).add_global_xpubs(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .add_global_xpubs()
+ .finish()
.unwrap();
let type_value = 0x01;
let (wallet, _, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_new_address().unwrap();
wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).add_global_xpubs(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .add_global_xpubs()
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).add_global_xpubs(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .add_global_xpubs()
+ .finish()
.unwrap();
let type_value = 0x01;
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
+
let tx = psbt.extract_tx();
let txid = tx.txid();
// skip saving the utxos, we know they can't be used anyways
details.transaction = Some(tx);
wallet.database.borrow_mut().set_tx(&details).unwrap();
- wallet.bump_fee(&txid, TxBuilder::new()).unwrap();
+ wallet.build_fee_bump(txid).unwrap().finish().unwrap();
}
#[test]
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(TxBuilder::with_recipients(vec![(
- addr.script_pubkey(),
- 25_000,
- )]))
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .finish()
.unwrap();
+
let tx = psbt.extract_tx();
let txid = tx.txid();
// skip saving the utxos, we know they can't be used anyways
details.height = Some(42);
wallet.database.borrow_mut().set_tx(&details).unwrap();
- wallet.bump_fee(&txid, TxBuilder::new()).unwrap();
+ wallet.build_fee_bump(txid).unwrap().finish().unwrap();
}
#[test]
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf()
+ .finish()
.unwrap();
+
let tx = psbt.extract_tx();
let txid = tx.txid();
// skip saving the utxos, we know they can't be used anyways
wallet.database.borrow_mut().set_tx(&details).unwrap();
wallet
- .bump_fee(
- &txid,
- TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(1.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(1.0))
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf()
+ .finish()
.unwrap();
+
let tx = psbt.extract_tx();
let txid = tx.txid();
// skip saving the utxos, we know they can't be used anyways
wallet.database.borrow_mut().set_tx(&details).unwrap();
wallet
- .bump_fee(&txid, TxBuilder::new().fee_absolute(10))
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_absolute(10)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_new_address().unwrap();
let (psbt, mut details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf()
+ .finish()
.unwrap();
+
let tx = psbt.extract_tx();
let txid = tx.txid();
// skip saving the utxos, we know they can't be used anyways
wallet.database.borrow_mut().set_tx(&details).unwrap();
wallet
- .bump_fee(&txid, TxBuilder::new().fee_absolute(0))
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_absolute(0)
+ .finish()
.unwrap();
}
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
.unwrap();
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(2.5)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(2.5))
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 25_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
.unwrap();
let (psbt, details) = wallet
- .bump_fee(&txid, TxBuilder::new().fee_absolute(200))
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_absolute(200)
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .enable_rbf(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
.unwrap();
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new()
- .maintain_single_recipient()
- .fee_rate(FeeRate::from_sat_per_vb(2.5)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(2.5))
+ .maintain_single_recipient()
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent);
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet()
- .enable_rbf(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
.unwrap();
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new()
- .maintain_single_recipient()
- .fee_absolute(300),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .maintain_single_recipient()
+ .fee_absolute(300)
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent);
};
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .utxos(vec![outpoint])
- .manually_selected_only()
- .enable_rbf(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .add_utxo(outpoint)
+ .unwrap()
+ .manually_selected_only()
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
// for the new feerate, it should be enough to reduce the output, but since we specify
// `drain_wallet` we expect to spend everything
let (_, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new()
- .drain_wallet()
- .maintain_single_recipient()
- .fee_rate(FeeRate::from_sat_per_vb(5.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .drain_wallet()
+ .maintain_single_recipient()
+ .fee_rate(FeeRate::from_sat_per_vb(5.0))
+ .finish()
.unwrap();
assert_eq!(details.sent, 75_000);
}
};
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .utxos(vec![outpoint])
- .manually_selected_only()
- .enable_rbf(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .add_utxo(outpoint)
+ .unwrap()
+ .manually_selected_only()
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
assert_eq!(original_details.sent, 25_000);
wallet
- .bump_fee(
- &txid,
- TxBuilder::new()
- .utxos(vec![outpoint])
- .manually_selected_only()
- .fee_rate(FeeRate::from_sat_per_vb(225.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .add_utxo(outpoint)
+ .unwrap()
+ .manually_selected_only()
+ .fee_rate(FeeRate::from_sat_per_vb(255.0))
+ .finish()
.unwrap();
}
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
.unwrap();
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(50.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(50.0))
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent + 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
.unwrap();
let (psbt, details) = wallet
- .bump_fee(&txid, TxBuilder::new().fee_absolute(6_000))
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_absolute(6_000)
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent + 25_000);
// initially make a tx without change by using `set_single_recipient`
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .add_utxo(OutPoint {
- txid: incoming_txid,
- vout: 0,
- })
- .manually_selected_only()
- .enable_rbf(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .add_utxo(OutPoint {
+ txid: incoming_txid,
+ vout: 0,
+ })
+ .unwrap()
+ .manually_selected_only()
+ .enable_rbf()
+ .finish()
.unwrap();
+
let mut tx = psbt.extract_tx();
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
// now bump the fees without using `maintain_single_recipient`. the wallet should add an
// extra input and a change output, and leave the original output untouched
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(50.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(50.0))
+ .finish()
.unwrap();
let original_send_all_amount = original_details.sent - original_details.fees;
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
assert_eq!(tx.input.len(), 1);
.unwrap();
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(140.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(140.0))
+ .finish()
.unwrap();
assert_eq!(original_details.received, 5_000 - original_details.fees);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
// the new fee_rate is low enough that just reducing the change would be fine, but we force
// the addition of an extra input with `add_utxo()`
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new()
- .add_utxo(OutPoint {
- txid: incoming_txid,
- vout: 0,
- })
- .fee_rate(FeeRate::from_sat_per_vb(5.0)),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .add_utxo(OutPoint {
+ txid: incoming_txid,
+ vout: 0,
+ })
+ .unwrap()
+ .fee_rate(FeeRate::from_sat_per_vb(5.0))
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent + 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, mut original_details) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .enable_rbf()
+ .finish()
.unwrap();
let mut tx = psbt.extract_tx();
let txid = tx.txid();
// the new fee_rate is low enough that just reducing the change would be fine, but we force
// the addition of an extra input with `add_utxo()`
let (psbt, details) = wallet
- .bump_fee(
- &txid,
- TxBuilder::new()
- .add_utxo(OutPoint {
- txid: incoming_txid,
- vout: 0,
- })
- .fee_absolute(250),
- )
+ .build_fee_bump(txid)
+ .unwrap()
+ .add_utxo(OutPoint {
+ txid: incoming_txid,
+ vout: 0,
+ })
+ .unwrap()
+ .fee_absolute(250)
+ .finish()
.unwrap();
assert_eq!(details.sent, original_details.sent + 25_000);
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_new_address().unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_new_address().unwrap();
let (mut psbt, _) = wallet
- .create_tx(
- TxBuilder::new()
- .set_single_recipient(addr.script_pubkey())
- .drain_wallet(),
- )
+ .build_tx()
+ .set_single_recipient(addr.script_pubkey())
+ .drain_wallet()
+ .finish()
.unwrap();
psbt.inputs[0].hd_keypaths.clear();
let (wallet, _, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)])
- .include_output_redeem_witness_script(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .include_output_redeem_witness_script()
+ .finish()
.unwrap();
// p2sh-p2wsh transaction should contain both witness and redeem scripts
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let (mut psbt, _) = wallet
- .create_tx(
- TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)])
- .include_output_redeem_witness_script(),
- )
+ .build_tx()
+ .add_recipient(addr.script_pubkey(), 45_000)
+ .include_output_redeem_witness_script()
+ .finish()
.unwrap();
// add another input to the psbt that is at least passable.
//! # use bdk::*;
//! # use bdk::wallet::tx_builder::CreateTx;
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
-//! // Create a transaction with one output to `to_address` of 50_000 satoshi, with a custom fee rate
-//! // of 5.0 satoshi/vbyte, only spending non-change outputs and with RBF signaling
-//! // enabled
-//! let builder = TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
+//! # let wallet = doctest_wallet!();
+//! // create a TxBuilder from a wallet
+//! let mut tx_builder = wallet.build_tx();
+//!
+//! let (psbt, tx_details) = tx_builder
+//! // Create a transaction with one output to `to_address` of 50_000 satoshi
+//! .add_recipient(to_address.script_pubkey(), 50_000)
+//! // With a custom fee rate of 5.0 satoshi/vbyte
//! .fee_rate(FeeRate::from_sat_per_vb(5.0))
+//! // Only spend non-change outputs
//! .do_not_spend_change()
-//! .enable_rbf();
-//! # let builder: TxBuilder<bdk::database::MemoryDatabase, _, CreateTx> = builder;
+//! // Turn on RBF signaling
+//! .enable_rbf()
+//! .finish()?;
+//!
+//! # Ok::<(), bdk::Error>(())
//! ```
use std::collections::BTreeMap;
use std::default::Default;
use std::marker::PhantomData;
+use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{OutPoint, Script, SigHashType, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
-use crate::database::Database;
-use crate::types::{FeeRate, KeychainKind, UTXO};
-
+use crate::{database::BatchDatabase, Error, Wallet};
+use crate::{
+ types::{FeeRate, KeychainKind, UTXO},
+ TransactionDetails,
+};
/// Context in which the [`TxBuilder`] is valid
pub trait TxBuilderContext: std::fmt::Debug + Default + Clone {}
-/// [`Wallet::create_tx`](super::Wallet::create_tx) context
+/// Marker type to indicate the [`TxBuilder`] is being used to create a new transaction (as opposed
+/// to bumping the fee of an existing one).
#[derive(Debug, Default, Clone)]
pub struct CreateTx;
impl TxBuilderContext for CreateTx {}
-/// [`Wallet::bump_fee`](super::Wallet::bump_fee) context
+/// Marker type to indicate the [`TxBuilder`] is being used to bump the fee of an existing transaction.
#[derive(Debug, Default, Clone)]
pub struct BumpFee;
impl TxBuilderContext for BumpFee {}
/// A transaction builder
///
-/// This structure contains the configuration that the wallet must follow to build a transaction.
+/// A `TxBuilder` is initially created by calling [`build_tx`] or [`build_fee_bump`] on a wallet.
+/// From there you set sepcific options on the builder until finally calling [`finish`] to get the transaction.
///
-/// For an example see [this module](super::tx_builder)'s documentation;
-#[derive(Debug)]
-pub struct TxBuilder<D: Database, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> {
+/// Each method on TxBuilder takes and returns `&mut self` so you can use either use a chaining call
+/// or assign the builder and call normally as in the following example:
+///
+/// ```
+/// # use bdk::*;
+/// # use bdk::wallet::tx_builder::*;
+/// # use bitcoin::*;
+/// # use core::str::FromStr;
+/// # let wallet = doctest_wallet!();
+/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
+/// # let addr2 = addr1.clone();
+/// // chaining
+/// let (psbt1, details) = wallet.build_tx()
+/// .ordering(TxOrdering::Untouched)
+/// .add_recipient(addr1.script_pubkey(), 50_000)
+/// .add_recipient(addr2.script_pubkey(), 50_000)
+/// .finish()?;
+///
+/// // non-chaining
+/// let mut builder = wallet.build_tx();
+/// for addr in &[addr1, addr2] {
+/// builder.add_recipient(addr.script_pubkey(), 50_000);
+/// }
+/// let (psbt2, details) = builder.ordering(TxOrdering::Untouched).finish()?;
+/// //
+/// assert_eq!(psbt1.global.unsigned_tx.output[..2], psbt2.global.unsigned_tx.output[..2]);
+/// # Ok::<(), bdk::Error>(())
+/// ```
+///
+/// At the moment [`coin_selection`] is an exception - it consumes `self`.
+/// This means it is usually best to call [`coin_selection`] first before calling other methods.
+///
+/// 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;
+///
+/// [`build_tx`]: Self::build_tx
+/// [`build_fee_bump`]: Self::build_fee_bump
+/// [`finish`]: Self::finish
+/// [`coin_selection`]: Self::coin_selection
+pub struct TxBuilder<'a, B, D, Cs, Ctx> {
+ pub(crate) params: Option<TxParams>,
+ pub(crate) wallet: &'a Wallet<B, D>,
+ pub(crate) coin_selection: Option<Cs>,
+ pub(crate) phantom: PhantomData<Ctx>,
+}
+
+/// The parameters for transaction creation sans coin selection algorithm.
+//TODO: TxParams should eventually be exposed publicly.
+#[derive(Default, Debug)]
+pub(crate) struct TxParams {
pub(crate) recipients: Vec<(Script, u64)>,
pub(crate) drain_wallet: bool,
pub(crate) single_recipient: Option<Script>,
pub(crate) fee_policy: Option<FeePolicy>,
pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
- pub(crate) utxos: Vec<OutPoint>,
+ pub(crate) utxos: BTreeMap<OutPoint, (UTXO, usize)>,
pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) manually_selected_only: bool,
pub(crate) sighash: Option<SigHashType>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) force_non_witness_utxo: bool,
pub(crate) add_global_xpubs: bool,
- pub(crate) coin_selection: Cs,
pub(crate) include_output_redeem_witness_script: bool,
+ pub(crate) bumping_fee: Option<PreviousFee>,
+}
- phantom: PhantomData<(D, Ctx)>,
+#[derive(Clone, Copy, Debug)]
+pub(crate) struct PreviousFee {
+ pub absolute: u64,
+ pub rate: f32,
}
#[derive(Debug)]
}
}
-// Unfortunately derive doesn't work with `PhantomData`: https://github.com/rust-lang/rust/issues/26925
-impl<D: Database, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> Default
- for TxBuilder<D, Cs, Ctx>
-where
- Cs: Default,
+// methods supported by both contexts, for any CoinSelectionAlgorithm
+impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
+ TxBuilder<'a, B, D, Cs, Ctx>
{
- fn default() -> Self {
- TxBuilder {
- recipients: Default::default(),
- drain_wallet: Default::default(),
- single_recipient: Default::default(),
- fee_policy: Default::default(),
- internal_policy_path: Default::default(),
- external_policy_path: Default::default(),
- utxos: Default::default(),
- unspendable: Default::default(),
- manually_selected_only: Default::default(),
- sighash: Default::default(),
- ordering: Default::default(),
- locktime: Default::default(),
- rbf: Default::default(),
- version: Default::default(),
- change_policy: Default::default(),
- force_non_witness_utxo: Default::default(),
- add_global_xpubs: Default::default(),
- coin_selection: Default::default(),
- include_output_redeem_witness_script: Default::default(),
-
- phantom: PhantomData,
- }
+ fn params(&mut self) -> &mut TxParams {
+ self.params
+ .as_mut()
+ .expect("method called on transaction builder after it was finalized")
}
-}
-
-// methods supported by both contexts, but only for `DefaultCoinSelectionAlgorithm`
-impl<D: Database, Ctx: TxBuilderContext> TxBuilder<D, DefaultCoinSelectionAlgorithm, Ctx> {
- /// Create an empty builder
- pub fn new() -> Self {
- Self::default()
- }
-}
-
-// methods supported by both contexts, for any CoinSelectionAlgorithm
-impl<D: Database, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> TxBuilder<D, Cs, Ctx> {
/// Set a custom fee rate
- pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
- self.fee_policy = Some(FeePolicy::FeeRate(fee_rate));
+ pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
+ self.params().fee_policy = Some(FeePolicy::FeeRate(fee_rate));
self
}
/// Set an absolute fee
- pub fn fee_absolute(mut self, fee_amount: u64) -> Self {
- self.fee_policy = Some(FeePolicy::FeeAmount(fee_amount));
+ pub fn fee_absolute(&mut self, fee_amount: u64) -> &mut Self {
+ self.params().fee_policy = Some(FeePolicy::FeeAmount(fee_amount));
self
}
/// # use bitcoin::*;
/// # use bdk::*;
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
+ /// # let wallet = doctest_wallet!();
/// let mut path = BTreeMap::new();
/// path.insert("aabbccdd".to_string(), vec![0, 1]);
///
- /// let builder = TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
+ /// let builder = wallet.build_tx()
+ /// .add_recipient(to_address.script_pubkey(), 50_000)
/// .policy_path(path, KeychainKind::External);
- /// # let builder: TxBuilder<bdk::database::MemoryDatabase, _, _> = builder;
+ ///
+ /// # Ok::<(), bdk::Error>(())
/// ```
pub fn policy_path(
- mut self,
+ &mut self,
policy_path: BTreeMap<String, Vec<usize>>,
keychain: KeychainKind,
- ) -> Self {
+ ) -> &mut Self {
let to_update = match keychain {
- KeychainKind::Internal => &mut self.internal_policy_path,
- KeychainKind::External => &mut self.external_policy_path,
+ KeychainKind::Internal => &mut self.params().internal_policy_path,
+ KeychainKind::External => &mut self.params().external_policy_path,
};
*to_update = Some(policy_path);
self
}
- /// Replace the internal list of utxos that **must** be spent with a new list
- ///
- /// 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.
- pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
- self.utxos = utxos;
- self
- }
-
/// Add a utxo to the internal list of utxos that **must** be spent
///
/// 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.
- pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
- self.utxos.push(utxo);
- self
+ pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, Error> {
+ if self.params().utxos.get(&outpoint).is_none() {
+ 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 descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
+ let satisfaction_weight = descriptor.max_satisfaction_weight(deriv_ctx).unwrap();
+ self.params()
+ .utxos
+ .insert(outpoint, (utxo, satisfaction_weight));
+ }
+ Ok(self)
}
- /// Only spend utxos added by [`add_utxo`] and [`utxos`].
+ /// Only spend utxos added by [`add_utxo`].
///
/// The wallet will **not** add additional utxos to the transaction even if they are needed to
/// make the transaction valid.
///
/// [`add_utxo`]: Self::add_utxo
- /// [`utxos`]: Self::utxos
- pub fn manually_selected_only(mut self) -> Self {
- self.manually_selected_only = true;
+ pub fn manually_selected_only(&mut self) -> &mut Self {
+ self.params().manually_selected_only = true;
self
}
/// Replace the internal list of unspendable utxos with a new list
///
- /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
- /// [`TxBuilder::add_utxo`] have priority over these. See the docs of the two linked methods
- /// for more details.
- pub fn unspendable(mut self, unspendable: Vec<OutPoint>) -> Self {
- self.unspendable = unspendable.into_iter().collect();
+ /// 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.
+ pub fn unspendable(&mut self, unspendable: Vec<OutPoint>) -> &mut Self {
+ self.params().unspendable = unspendable.into_iter().collect();
self
}
/// Add a utxo to the internal list of unspendable utxos
///
- /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
- /// [`TxBuilder::add_utxo`] have priority over this. See the docs of the two linked methods
- /// for more details.
- pub fn add_unspendable(mut self, unspendable: OutPoint) -> Self {
- self.unspendable.insert(unspendable);
+ /// 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.
+ pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self {
+ self.params().unspendable.insert(unspendable);
self
}
/// Sign with a specific sig hash
///
/// **Use this option very carefully**
- pub fn sighash(mut self, sighash: SigHashType) -> Self {
- self.sighash = Some(sighash);
+ pub fn sighash(&mut self, sighash: SigHashType) -> &mut Self {
+ self.params().sighash = Some(sighash);
self
}
/// Choose the ordering for inputs and outputs of the transaction
- pub fn ordering(mut self, ordering: TxOrdering) -> Self {
- self.ordering = ordering;
+ pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self {
+ self.params().ordering = ordering;
self
}
/// Use a specific nLockTime while creating the transaction
///
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
- pub fn nlocktime(mut self, locktime: u32) -> Self {
- self.locktime = Some(locktime);
+ pub fn nlocktime(&mut self, locktime: u32) -> &mut Self {
+ self.params().locktime = Some(locktime);
self
}
///
/// The `version` should always be greater than `0` and greater than `1` if the wallet's
/// descriptors contain an "older" (OP_CSV) operator.
- pub fn version(mut self, version: i32) -> Self {
- self.version = Some(Version(version));
+ pub fn version(&mut self, version: i32) -> &mut Self {
+ self.params().version = Some(Version(version));
self
}
///
/// This effectively adds all the change outputs to the "unspendable" list. See
/// [`TxBuilder::unspendable`].
- pub fn do_not_spend_change(mut self) -> Self {
- self.change_policy = ChangeSpendPolicy::ChangeForbidden;
+ pub fn do_not_spend_change(&mut self) -> &mut Self {
+ self.params().change_policy = ChangeSpendPolicy::ChangeForbidden;
self
}
///
/// This effectively adds all the non-change outputs to the "unspendable" list. See
/// [`TxBuilder::unspendable`].
- pub fn only_spend_change(mut self) -> Self {
- self.change_policy = ChangeSpendPolicy::OnlyChange;
+ pub fn only_spend_change(&mut self) -> &mut Self {
+ self.params().change_policy = ChangeSpendPolicy::OnlyChange;
self
}
/// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
/// [`TxBuilder::only_spend_change`] for some shortcuts.
- pub fn change_policy(mut self, change_policy: ChangeSpendPolicy) -> Self {
- self.change_policy = change_policy;
+ pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self {
+ self.params().change_policy = change_policy;
self
}
/// descriptors.
///
/// This is useful for signers which always require it, like Trezor hardware wallets.
- pub fn force_non_witness_utxo(mut self) -> Self {
- self.force_non_witness_utxo = true;
+ pub fn force_non_witness_utxo(&mut self) -> &mut Self {
+ self.params().force_non_witness_utxo = true;
self
}
/// [`psbt::Output::witness_script`](bitcoin::util::psbt::Output::witness_script) fields.
///
/// This is useful for signers which always require it, like ColdCard hardware wallets.
- pub fn include_output_redeem_witness_script(mut self) -> Self {
- self.include_output_redeem_witness_script = true;
+ pub fn include_output_redeem_witness_script(&mut self) -> &mut Self {
+ self.params().include_output_redeem_witness_script = true;
self
}
///
/// This is useful for offline signers that take part to a multisig. Some hardware wallets like
/// BitBox and ColdCard are known to require this.
- pub fn add_global_xpubs(mut self) -> Self {
- self.add_global_xpubs = true;
+ pub fn add_global_xpubs(&mut self) -> &mut Self {
+ self.params().add_global_xpubs = true;
self
}
/// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and the change policy.
- pub fn drain_wallet(mut self) -> Self {
- self.drain_wallet = true;
+ pub fn drain_wallet(&mut self) -> &mut Self {
+ self.params().drain_wallet = true;
self
}
/// Choose the coin selection algorithm
///
/// Overrides the [`DefaultCoinSelectionAlgorithm`](super::coin_selection::DefaultCoinSelectionAlgorithm).
+ ///
+ /// Note that this function consumes the builder and returns it so it is usually best to put this as the first call on the builder.
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
self,
coin_selection: P,
- ) -> TxBuilder<D, P, Ctx> {
+ ) -> TxBuilder<'a, B, D, P, Ctx> {
TxBuilder {
- recipients: self.recipients,
- drain_wallet: self.drain_wallet,
- single_recipient: self.single_recipient,
- fee_policy: self.fee_policy,
- internal_policy_path: self.internal_policy_path,
- external_policy_path: self.external_policy_path,
- utxos: self.utxos,
- unspendable: self.unspendable,
- manually_selected_only: self.manually_selected_only,
- sighash: self.sighash,
- ordering: self.ordering,
- locktime: self.locktime,
- rbf: self.rbf,
- version: self.version,
- change_policy: self.change_policy,
- force_non_witness_utxo: self.force_non_witness_utxo,
- add_global_xpubs: self.add_global_xpubs,
- include_output_redeem_witness_script: self.include_output_redeem_witness_script,
- coin_selection,
-
+ wallet: self.wallet,
+ params: self.params,
+ coin_selection: Some(coin_selection),
phantom: PhantomData,
}
}
-}
-// methods supported only by create_tx, and only for `DefaultCoinSelectionAlgorithm`
-impl<D: Database> TxBuilder<D, DefaultCoinSelectionAlgorithm, CreateTx> {
- /// Create a builder starting from a list of recipients
- pub fn with_recipients(recipients: Vec<(Script, u64)>) -> Self {
- Self::default().set_recipients(recipients)
+ /// Finish the building the transaction.
+ ///
+ /// Returns the [`BIP174`] "PSBT" and summary details about the transaction.
+ ///
+ /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
+ pub fn finish(&mut self) -> Result<(PSBT, TransactionDetails), Error> {
+ self.wallet.create_tx(
+ self.coin_selection.take().unwrap(),
+ self.params.take().unwrap(),
+ )
}
}
-// methods supported only by create_tx, for any `CoinSelectionAlgorithm`
-impl<D: Database, Cs: CoinSelectionAlgorithm<D>> TxBuilder<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
- pub fn set_recipients(mut self, recipients: Vec<(Script, u64)>) -> Self {
- self.recipients = recipients;
+ pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
+ self.params().recipients = recipients;
self
}
/// Add a recipient to the internal list
- pub fn add_recipient(mut self, script_pubkey: Script, amount: u64) -> Self {
- self.recipients.push((script_pubkey, amount));
+ pub fn add_recipient(&mut self, script_pubkey: Script, amount: u64) -> &mut Self {
+ self.params().recipients.push((script_pubkey, amount));
self
}
/// It can only be used in conjunction with [`drain_wallet`](Self::drain_wallet) to send the
/// entire content of the wallet (minus filters) to a single recipient or with a
/// list of manually selected UTXOs by enabling [`manually_selected_only`](Self::manually_selected_only)
- /// and selecting them with [`utxos`](Self::utxos) or [`add_utxo`](Self::add_utxo).
+ /// and selecting them with or [`add_utxo`](Self::add_utxo).
///
/// When bumping the fees of a transaction made with this option, the user should remeber to
/// add [`maintain_single_recipient`](Self::maintain_single_recipient) to correctly update the
/// single output instead of adding one more for the change.
- pub fn set_single_recipient(mut self, recipient: Script) -> Self {
- self.single_recipient = Some(recipient);
- self.recipients.clear();
+ pub fn set_single_recipient(&mut self, recipient: Script) -> &mut Self {
+ self.params().single_recipient = Some(recipient);
+ self.params().recipients.clear();
self
}
/// Enable signaling RBF
///
/// This will use the default nSequence value of `0xFFFFFFFD`.
- pub fn enable_rbf(mut self) -> Self {
- self.rbf = Some(RBFValue::Default);
+ pub fn enable_rbf(&mut self) -> &mut Self {
+ self.params().rbf = Some(RBFValue::Default);
self
}
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
- pub fn enable_rbf_with_sequence(mut self, nsequence: u32) -> Self {
- self.rbf = Some(RBFValue::Value(nsequence));
+ pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
+ self.params().rbf = Some(RBFValue::Value(nsequence));
self
}
}
// methods supported only by bump_fee
-impl<D: Database> TxBuilder<D, DefaultCoinSelectionAlgorithm, BumpFee> {
+impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> {
/// Bump the fees of a transaction made with [`set_single_recipient`](Self::set_single_recipient)
///
- /// Unless extra inputs are specified with [`add_utxo`] or [`utxos`], this flag will make
+ /// Unless extra inputs are specified with [`add_utxo`], this flag will make
/// `bump_fee` reduce the value of the existing output, or fail if it would be consumed
/// entirely given the higher new fee rate.
///
/// Fails if the transaction has more than one outputs.
///
/// [`add_utxo`]: Self::add_utxo
- /// [`utxos`]: Self::utxos
- pub fn maintain_single_recipient(mut self) -> Self {
- self.single_recipient = Some(Script::default());
+ pub fn maintain_single_recipient(&mut self) -> &mut Self {
+ 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");
+ 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.
self
}
}