diff --git a/lwk_wollet/src/tx_builder.rs b/lwk_wollet/src/tx_builder.rs index 843f4d1a..14234780 100644 --- a/lwk_wollet/src/tx_builder.rs +++ b/lwk_wollet/src/tx_builder.rs @@ -1172,16 +1172,23 @@ 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). + // 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 || satoshi_out == 0 { + 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 +1210,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 +1221,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 ceaf503f..ca856ce5 100644 --- a/lwk_wollet/tests/e2e.rs +++ b/lwk_wollet/tests/e2e.rs @@ -1367,6 +1367,53 @@ 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() + .drain_lbtc_wallet() + .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() {