From 5437c54c1838f078361e9bed0e24c41c8c343df4 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 15 Dec 2025 09:03:35 +0100 Subject: [PATCH 1/3] wollet: test send all fee --- lwk_wollet/tests/e2e.rs | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lwk_wollet/tests/e2e.rs b/lwk_wollet/tests/e2e.rs index ceaf503f..43c7ee08 100644 --- a/lwk_wollet/tests/e2e.rs +++ b/lwk_wollet/tests/e2e.rs @@ -1367,6 +1367,52 @@ fn drain() { } } +#[test] +fn send_all_vs_send_balance_minus_fee() { + // Create a send_all transaction to extract the fee, then verify that sending + // (balance - fee) with a normal transaction results in the same outcome. + let env = TestEnvBuilder::from_env().with_electrum().build(); + let signer = generate_signer(); + let view_key = generate_view_key(); + let desc = format!("ct({},elwpkh({}/*))", view_key, signer.xpub()); + let signer = AnySigner::Software(signer); + + let client = test_client_electrum(&env.electrum_url()); + let mut wallet = TestWollet::new(client, &desc); + + // Fund the wallet + wallet.fund_btc(&env); + + let node_address = env.elementsd_getnewaddress(); + let balance = wallet.balance_btc(); + + // Create a send_all transaction without broadcasting to extract the fee + let pset_send_all = wallet + .tx_builder() + .drain_lbtc_wallet() + .drain_lbtc_to(node_address.clone()) + .finish() + .unwrap(); + + let details = wallet.wollet.get_details(&pset_send_all).unwrap(); + let fee = details.balance.fee; + + // Create a normal send transaction with amount = balance - fee + let amount = balance - fee; + let mut pset = wallet + .tx_builder() + .add_lbtc_recipient(&node_address, amount) + .unwrap() + .finish() + .unwrap(); + + wallet.sign(&signer, &mut pset); + wallet.send(&mut pset); + + // Verify the wallet is now empty + assert_eq!(wallet.balance_btc(), 0); +} + fn wait_tx_update(wallet: &mut TestWollet) { for _ in 0..50 { if let Some(update) = wallet.client.full_scan(&wallet.wollet).unwrap() { From f5cf7753fb7689d26ac7f4e86cec543d8cd70f6f Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 15 Dec 2025 09:26:47 +0100 Subject: [PATCH 2/3] wollet: when drain is activate, don't create a change --- lwk_wollet/src/tx_builder.rs | 28 ++++++++++++++++++---------- lwk_wollet/tests/e2e.rs | 1 + 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lwk_wollet/src/tx_builder.rs b/lwk_wollet/src/tx_builder.rs index 843f4d1a..13279597 100644 --- a/lwk_wollet/src/tx_builder.rs +++ b/lwk_wollet/src/tx_builder.rs @@ -1172,16 +1172,21 @@ impl TxBuilder { }); } let satoshi_change = satoshi_in - satoshi_out - temp_fee; - let addressee = if let Some(address) = self.drain_to { - Recipient::from_address(satoshi_change, &address, wollet.policy_asset()) - } else { - wollet.addressee_change( + // Track whether we add a change/drain output (needed later for fee adjustment) + let has_change_output = self.drain_to.is_some() || !self.drain_lbtc; + if let Some(address) = self.drain_to { + let addressee = + Recipient::from_address(satoshi_change, &address, wollet.policy_asset()); + wollet.add_output(&mut pset, &addressee)?; + } else if !self.drain_lbtc { + let addressee = wollet.addressee_change( satoshi_change, wollet.policy_asset(), &mut last_unused_internal, - )? - }; - wollet.add_output(&mut pset, &addressee)?; + )?; + wollet.add_output(&mut pset, &addressee)?; + } + let fee_output = Output::new_explicit(Script::default(), temp_fee, wollet.policy_asset(), None); pset.add_output(fee_output); @@ -1203,7 +1208,7 @@ impl TxBuilder { let vsize = weight.div_ceil(4); let fee = (vsize as f32 * self.fee_rate / 1000.0).ceil() as u64; - if satoshi_in <= (satoshi_out + fee) { + if satoshi_in < (satoshi_out + fee) { return Err(Error::InsufficientFunds { missing_sats: (satoshi_out + fee + 1) - satoshi_in, // +1 to ensure we have more than just equal asset_id: wollet.policy_asset(), @@ -1214,8 +1219,11 @@ impl TxBuilder { // Replace change and fee outputs let n_outputs = pset.n_outputs(); let outputs = pset.outputs_mut(); - let change_output = &mut outputs[n_outputs - 2]; // index check: we always have the lbtc change and the fee output at least - change_output.amount = Some(satoshi_change); + // Update change output only if it exists (not when drain_lbtc without drain_to) + if has_change_output { + let change_output = &mut outputs[n_outputs - 2]; // index check: we always have the lbtc change and the fee output at least + change_output.amount = Some(satoshi_change); + } let fee_output = &mut outputs[n_outputs - 1]; fee_output.amount = Some(fee); diff --git a/lwk_wollet/tests/e2e.rs b/lwk_wollet/tests/e2e.rs index 43c7ee08..ca856ce5 100644 --- a/lwk_wollet/tests/e2e.rs +++ b/lwk_wollet/tests/e2e.rs @@ -1401,6 +1401,7 @@ fn send_all_vs_send_balance_minus_fee() { let amount = balance - fee; let mut pset = wallet .tx_builder() + .drain_lbtc_wallet() .add_lbtc_recipient(&node_address, amount) .unwrap() .finish() From a87e58a02245071ef631002233356c44379aa764 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 15 Dec 2025 11:08:59 +0100 Subject: [PATCH 3/3] wollet: fix drain_lbtc --- lwk_wollet/src/tx_builder.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lwk_wollet/src/tx_builder.rs b/lwk_wollet/src/tx_builder.rs index 13279597..14234780 100644 --- a/lwk_wollet/src/tx_builder.rs +++ b/lwk_wollet/src/tx_builder.rs @@ -1172,13 +1172,15 @@ impl TxBuilder { }); } let satoshi_change = satoshi_in - satoshi_out - temp_fee; - // Track whether we add a change/drain output (needed later for fee adjustment) - let has_change_output = self.drain_to.is_some() || !self.drain_lbtc; + // Track whether we add a change/drain output (needed later for fee adjustment). + // Skip change only when: drain_lbtc is set AND there are explicit recipients AND no drain_to. + // If there are no recipients (satoshi_out == 0), we always need a change/drain output. + let has_change_output = self.drain_to.is_some() || !self.drain_lbtc || satoshi_out == 0; if let Some(address) = self.drain_to { let addressee = Recipient::from_address(satoshi_change, &address, wollet.policy_asset()); wollet.add_output(&mut pset, &addressee)?; - } else if !self.drain_lbtc { + } else if !self.drain_lbtc || satoshi_out == 0 { let addressee = wollet.addressee_change( satoshi_change, wollet.policy_asset(),