Replace set_single_recipient with drain_to

What set_single_recipient does turns out to be useful with multiple
recipients.
Effectively, set_single_recipient was simply creating a change
output that was arbitrarily required to be the only output.
But what if you want to send excess funds to one address but still have
additional recipients who receive a fixed value?
Generalizing this to `drain_to` simplifies the logic and removes several
error cases while also allowing new use cases.

"maintain_single_recipient" is also replaced with "allow_shrinking"
which has more general semantics.
This commit is contained in:
LLFourn 2021-06-16 12:43:32 +10:00
parent c1077b95cf
commit 618e0d3700
No known key found for this signature in database
GPG Key ID: A27093B54DA11F65
4 changed files with 167 additions and 166 deletions

View File

@ -7,9 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Wallet
#### Added
- Bitcoin core RPC added as blockchain backend
- Add a `verify` feature that can be enable to verify the unconfirmed txs we download against the consensus rules
- Added Bitcoin core RPC added as blockchain backend
- Added a `verify` feature that can be enable to verify the unconfirmed txs we download against the consensus rules
- Removed and replaced `set_single_recipient` with more general `allow_shrinking`.
## [v0.8.0] - [v0.7.0]

View File

@ -24,10 +24,6 @@ pub enum Error {
Generic(String),
/// This error is thrown when trying to convert Bare and Public key script to address
ScriptDoesntHaveAddressForm,
/// Found multiple outputs when `single_recipient` option has been specified
SingleRecipientMultipleOutputs,
/// `single_recipient` option is selected but neither `drain_wallet` nor `manually_selected_only` are
SingleRecipientNoInputs,
/// Cannot build a tx without recipients
NoRecipients,
/// `manually_selected_only` option is selected but no utxo has been passed

View File

@ -565,21 +565,6 @@ where
output: vec![],
};
let recipients = match &params.single_recipient {
Some(recipient) => vec![(recipient, 0)],
None => params.recipients.iter().map(|(r, v)| (r, *v)).collect(),
};
if params.single_recipient.is_some()
&& !params.manually_selected_only
&& !params.drain_wallet
&& params.bumping_fee.is_none()
{
return Err(Error::SingleRecipientNoInputs);
}
if recipients.is_empty() {
return Err(Error::NoRecipients);
}
if params.manually_selected_only && params.utxos.is_empty() {
return Err(Error::NoUtxosSelected);
}
@ -591,12 +576,12 @@ where
let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
fee_amount += calc_fee_bytes(tx.get_weight());
for (index, (script_pubkey, satoshi)) in recipients.into_iter().enumerate() {
let value = match params.single_recipient {
Some(_) => 0,
None if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
None => satoshi,
};
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
for (index, (script_pubkey, value)) in recipients.enumerate() {
if value.is_dust() {
return Err(Error::OutputBelowDustLimit(index));
}
if self.is_mine(script_pubkey)? {
received += value;
@ -651,54 +636,47 @@ where
})
.collect();
// prepare the change output
let change_output = match params.single_recipient {
Some(_) => None,
None => {
let change_script = self.get_change_address()?;
let change_output = TxOut {
script_pubkey: change_script,
value: 0,
};
// prepare the drain output
let mut drain_output = {
let script_pubkey = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(),
None => self.get_change_address()?,
};
// take the change into account for fees
fee_amount += calc_fee_bytes(serialize(&change_output).len() * 4);
Some(change_output)
TxOut {
script_pubkey,
value: 0,
}
};
fee_amount += calc_fee_bytes(serialize(&drain_output).len() * 4);
let mut fee_amount = fee_amount.ceil() as u64;
let change_val = (coin_selection.selected_amount() - outgoing).saturating_sub(fee_amount);
let drain_val = (coin_selection.selected_amount() - outgoing).saturating_sub(fee_amount);
match change_output {
None if change_val.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,
});
}
Some(_) if change_val.is_dust() => {
// skip the change output because it's dust -- just include it in the fee.
fee_amount += change_val;
}
Some(mut change_output) => {
change_output.value = change_val;
received += change_val;
tx.output.push(change_output);
}
None => {
// there's only one output, send everything to it
tx.output[0].value = change_val;
// the single recipient is our address
if self.is_mine(&tx.output[0].script_pubkey)? {
received = change_val;
if tx.output.is_empty() {
if params.drain_to.is_some() {
if drain_val.is_dust() {
return Err(Error::InsufficientFunds {
needed: DUST_LIMIT_SATOSHI,
available: drain_val,
});
}
} else {
return Err(Error::NoRecipients);
}
}
if drain_val.is_dust() {
fee_amount += drain_val;
} else {
drain_output.value = drain_val;
if self.is_mine(&drain_output.script_pubkey)? {
received += drain_val;
}
tx.output.push(drain_output);
}
// sort input/outputs according to the chosen algorithm
params.ordering.sort_tx(&mut tx);
@ -725,10 +703,6 @@ where
/// *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.
///
/// ## Example
///
/// ```no_run
@ -2008,13 +1982,11 @@ pub(crate) mod test {
}
#[test]
fn test_create_tx_single_recipient_drain_wallet() {
fn test_create_tx_drain_wallet_and_drain_to() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, details) = builder.finish().unwrap();
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
@ -2024,6 +1996,33 @@ pub(crate) mod test {
);
}
#[test]
fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
let drain_addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 20_000)
.drain_to(drain_addr.script_pubkey())
.drain_wallet();
let (psbt, details) = builder.finish().unwrap();
dbg!(&psbt);
let outputs = psbt.global.unsigned_tx.output;
assert_eq!(outputs.len(), 2);
let main_output = outputs
.iter()
.find(|x| x.script_pubkey == addr.script_pubkey())
.unwrap();
let drain_output = outputs
.iter()
.find(|x| x.script_pubkey == drain_addr.script_pubkey())
.unwrap();
assert_eq!(main_output.value, 20_000,);
assert_eq!(drain_output.value, 30_000 - details.fee.unwrap_or(0));
}
#[test]
fn test_create_tx_default_fee_rate() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
@ -2054,7 +2053,7 @@ pub(crate) mod test {
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.drain_wallet()
.fee_absolute(100);
let (psbt, details) = builder.finish().unwrap();
@ -2073,7 +2072,7 @@ pub(crate) mod test {
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.drain_wallet()
.fee_absolute(0);
let (psbt, details) = builder.finish().unwrap();
@ -2093,7 +2092,7 @@ pub(crate) mod test {
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.drain_wallet()
.fee_absolute(60_000);
let (_psbt, _details) = builder.finish().unwrap();
@ -2134,13 +2133,13 @@ pub(crate) mod test {
#[test]
#[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_single_recipient_dust_amount() {
fn test_create_tx_drain_to_dust_amount() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New).unwrap();
// very high fee rate, so that the only output would be below dust
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.drain_wallet()
.fee_rate(FeeRate::from_sat_per_vb(453.0));
builder.finish().unwrap();
@ -2201,9 +2200,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1);
@ -2227,9 +2224,7 @@ pub(crate) mod test {
let addr = testutils!(@external descriptors, 5);
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1);
@ -2250,9 +2245,7 @@ pub(crate) mod test {
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert_eq!(
@ -2275,9 +2268,7 @@ pub(crate) mod test {
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.inputs[0].redeem_script, None);
@ -2300,9 +2291,7 @@ pub(crate) mod test {
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
let script = Script::from(
@ -2322,9 +2311,7 @@ pub(crate) mod test {
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
@ -2338,7 +2325,7 @@ pub(crate) mod test {
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.only_witness_utxo()
.drain_wallet();
let (psbt, _) = builder.finish().unwrap();
@ -2353,9 +2340,7 @@ pub(crate) mod test {
get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert!(psbt.inputs[0].witness_utxo.is_some());
@ -2367,9 +2352,7 @@ pub(crate) mod test {
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some());
@ -3003,7 +2986,7 @@ pub(crate) mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.drain_wallet()
.enable_rbf();
let (psbt, mut original_details) = builder.finish().unwrap();
@ -3027,7 +3010,7 @@ pub(crate) mod test {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder
.fee_rate(FeeRate::from_sat_per_vb(2.5))
.maintain_single_recipient()
.allow_shrinking(addr.script_pubkey())
.unwrap();
let (psbt, details) = builder.finish().unwrap();
@ -3047,7 +3030,7 @@ pub(crate) mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.drain_wallet()
.enable_rbf();
let (psbt, mut original_details) = builder.finish().unwrap();
@ -3070,7 +3053,7 @@ pub(crate) mod test {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder
.maintain_single_recipient()
.allow_shrinking(addr.script_pubkey())
.unwrap()
.fee_absolute(300);
let (psbt, details) = builder.finish().unwrap();
@ -3101,7 +3084,7 @@ pub(crate) mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.add_utxo(outpoint)
.unwrap()
.manually_selected_only()
@ -3130,7 +3113,7 @@ pub(crate) mod test {
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder
.drain_wallet()
.maintain_single_recipient()
.allow_shrinking(addr.script_pubkey())
.unwrap()
.fee_rate(FeeRate::from_sat_per_vb(5.0));
let (_, details) = builder.finish().unwrap();
@ -3145,7 +3128,7 @@ pub(crate) mod test {
// them, and make sure that `bump_fee` doesn't try to add more. This fails because we've
// told the wallet it's not allowed to add more inputs AND it can't reduce the value of the
// existing output. In other words, bump_fee + manually_selected_only is always an error
// unless you've also set "maintain_single_recipient".
// unless you've also set "allow_shrinking OR there is a change output".
let incoming_txid = crate::populate_test_db!(
wallet.database.borrow_mut(),
testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
@ -3158,7 +3141,7 @@ pub(crate) mod test {
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.add_utxo(outpoint)
.unwrap()
.manually_selected_only()
@ -3324,11 +3307,11 @@ pub(crate) mod test {
Some(100),
);
// initially make a tx without change by using `set_single_recipient`
// initially make a tx without change by using `set_drain_recipient`
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.add_utxo(OutPoint {
txid: incoming_txid,
vout: 0,
@ -3356,7 +3339,7 @@ pub(crate) mod test {
.set_tx(&original_details)
.unwrap();
// now bump the fees without using `maintain_single_recipient`. the wallet should add an
// now bump the fees without using `allow_shrinking`. the wallet should add an
// extra input and a change output, and leave the original output untouched
let mut builder = wallet.build_fee_bump(txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
@ -3602,9 +3585,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
@ -3619,9 +3600,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
@ -3636,9 +3615,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
@ -3653,9 +3630,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
@ -3671,9 +3646,7 @@ pub(crate) mod test {
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
@ -3688,9 +3661,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_wallet();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
psbt.inputs[0].bip32_derivation.clear();
@ -3772,7 +3743,7 @@ pub(crate) mod test {
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.drain_to(addr.script_pubkey())
.sighash(sighash)
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();

View File

@ -133,7 +133,7 @@ pub struct TxBuilder<'a, B, D, Cs, Ctx> {
pub(crate) struct TxParams {
pub(crate) recipients: Vec<(Script, u64)>,
pub(crate) drain_wallet: bool,
pub(crate) single_recipient: Option<Script>,
pub(crate) drain_to: 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>>>,
@ -560,49 +560,82 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
self
}
/// Set a single recipient that will get all the selected funds minus the fee. No change will
/// be created
/// Sets the address to *drain* excess coins to.
///
/// This method overrides any recipient set with [`set_recipients`](Self::set_recipients) or
/// [`add_recipient`](Self::add_recipient).
///
/// 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 or [`add_utxo`](Self::add_utxo).
/// Usually, when there is excess coins they are sent to a change address generated by the
/// wallet. This option replaces the usual change address with an arbitrary `script_pubkey` of
/// your choosing. Just as with a change output, if the drain output is not needed (the excess
/// coins are too small) it will not be included in the resulting transaction. The only
/// difference is that it is valid to use `drain_to` without setting any ordinary recipients
/// with [`add_recipient`] (but it is perfectly find to add recipients as well).
///
/// 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
/// add [`allow_shrinking`] to correctly update the
/// single output instead of adding one more for the change.
pub fn set_single_recipient(&mut self, recipient: Script) -> &mut Self {
self.params.single_recipient = Some(recipient);
self.params.recipients.clear();
///
/// # Example
///
/// `drain_to` is very useful for draining all the coins in a wallet with [`drain_wallet`] to a
/// single address.
///
/// ```
/// # use std::str::FromStr;
/// # use bitcoin::*;
/// # use bdk::*;
/// # use bdk::wallet::tx_builder::CreateTx;
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let wallet = doctest_wallet!();
/// let mut tx_builder = wallet.build_tx();
///
/// tx_builder
/// // spend all outputs in this wallet
/// .drain_wallet()
/// // send the excess (which is all the coins minus the fee) to this address
/// .drain_to(to_address.script_pubkey())
/// .fee_rate(FeeRate::from_sat_per_vb(5.0))
/// .enable_rbf();
/// let (psbt, tx_details) = tx_builder.finish()?;
/// # Ok::<(), bdk::Error>(())
/// ```
///
/// [`allow_shrinking`]: Self::allow_shrinking
/// [`add_recipient`]: Self::add_recipient
/// [`drain_wallet`]: Self::drain_wallet
pub fn drain_to(&mut self, script_pubkey: Script) -> &mut Self {
self.params.drain_to = Some(script_pubkey);
self
}
}
// methods supported only by bump_fee
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)
/// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
/// will attempt to find a change output to shrink instead.
///
/// 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.
/// **Note** that the output may shrunk to below the dust limit and therefore removed. If it is
/// preserved then it is currently not guaranteed to be in the same position as it was
/// originally.
///
/// If extra inputs are added and they are not entirely consumed in fees, a change output will not
/// be added; the existing output will simply grow in value.
///
/// Fails if the transaction has more than one outputs.
///
/// [`add_utxo`]: Self::add_utxo
pub fn maintain_single_recipient(&mut self) -> Result<&mut Self, Error> {
let mut recipients = self.params.recipients.drain(..).collect::<Vec<_>>();
if recipients.len() != 1 {
return Err(Error::SingleRecipientMultipleOutputs);
/// Returns an `Err` if `script_pubkey` can't be found among the recipients of the
/// transaction we are bumping.
pub fn allow_shrinking(&mut self, script_pubkey: Script) -> Result<&mut Self, Error> {
match self
.params
.recipients
.iter()
.position(|(recipient_script, _)| *recipient_script == script_pubkey)
{
Some(position) => {
self.params.recipients.remove(position);
self.params.drain_to = Some(script_pubkey);
Ok(self)
}
None => Err(Error::Generic(format!(
"{} was not in the original transaction",
script_pubkey
))),
}
self.params.single_recipient = Some(recipients.pop().unwrap().0);
Ok(self)
}
}