From 1b33e74a7a8b1d185a3aaea84a3ec6520ee05976 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Fri, 20 Dec 2024 14:58:53 +0000 Subject: [PATCH 1/3] refactor: add `coin_is_owned` method --- liana-gui/src/app/state/mod.rs | 4 ++-- liana-gui/src/daemon/model.rs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/liana-gui/src/app/state/mod.rs b/liana-gui/src/app/state/mod.rs index 19a1d59d..e76c274b 100644 --- a/liana-gui/src/app/state/mod.rs +++ b/liana-gui/src/app/state/mod.rs @@ -29,7 +29,7 @@ use super::{ pub const HISTORY_EVENT_PAGE_SIZE: u64 = 20; -use crate::daemon::model::LabelsLoader; +use crate::daemon::model::{coin_is_owned, LabelsLoader}; use crate::daemon::{ model::{remaining_sequence, Coin, HistoryTransaction, Payment}, Daemon, @@ -94,7 +94,7 @@ fn coins_summary( for coin in coins { if coin.spend_info.is_none() { // Include unconfirmed coins from self in confirmed balance. - if coin.block_height.is_some() || coin.is_from_self { + if coin_is_owned(coin) { balance += coin.amount; // Only consider confirmed coins for remaining seq // (they would not be considered as expiring so we can also skip that part) diff --git a/liana-gui/src/daemon/model.rs b/liana-gui/src/daemon/model.rs index c4657ba9..9bbb0b94 100644 --- a/liana-gui/src/daemon/model.rs +++ b/liana-gui/src/daemon/model.rs @@ -29,6 +29,13 @@ pub fn remaining_sequence(coin: &Coin, blockheight: u32, timelock: u16) -> u32 { } } +/// Whether the coin is owned by this wallet. +/// This comprises all confirmed coins together with those +/// unconfirmed coins from self. +pub fn coin_is_owned(coin: &Coin) -> bool { + coin.block_height.is_some() || coin.is_from_self +} + #[derive(Debug, Clone)] pub struct SpendTx { pub network: Network, From 021c8e2a43239c39fffac26dc2f8a04eda3efda0 Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Sat, 14 Dec 2024 15:08:05 +0000 Subject: [PATCH 2/3] gui(spend): improve insufficient funds handling The coins selected should correspond to the missing amount. --- liana-gui/src/app/state/spend/step.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/liana-gui/src/app/state/spend/step.rs b/liana-gui/src/app/state/spend/step.rs index e1079c4d..506214b3 100644 --- a/liana-gui/src/app/state/spend/step.rs +++ b/liana-gui/src/app/state/spend/step.rs @@ -21,7 +21,7 @@ use liana_ui::{component::form, widget::Element}; use crate::{ app::{cache::Cache, error::Error, message::Message, state::psbt, view, wallet::Wallet}, daemon::{ - model::{remaining_sequence, Coin, CreateSpendResult, SpendTx}, + model::{coin_is_owned, remaining_sequence, Coin, CreateSpendResult, SpendTx}, Daemon, }, }; @@ -354,13 +354,16 @@ impl DefineSpend { ); } } - // For coin selection error (insufficient funds), do not make any changes to - // selected coins on screen and just show user how much is left to select. - // User can then either: - // - modify recipient amounts and/or feerate and let coin selection run again, or - // - select coins manually. Ok(CreateSpendResult::InsufficientFunds { missing }) => { self.amount_left_to_select = Some(Amount::from_sat(missing)); + if !self.is_user_coin_selection { + // The missing amount is based on all candidates for coin selection + // being used, which are all coins if there's a recipient with max + // or otherwise all owned coins. + for (coin, selected) in &mut self.coins { + *selected = self.send_max_to_recipient.is_some() || coin_is_owned(coin); + } + } if let Some((i, recipient)) = recipient_with_max { let amount = Amount::from_sat(if destinations.is_empty() { // If there are no other recipients, then the missing value will From e8d2fe2717583c071fc878b6d3f1fdb42c120bed Mon Sep 17 00:00:00 2001 From: Michael Mallan Date: Fri, 20 Dec 2024 14:40:13 +0000 Subject: [PATCH 3/3] send max from all owned coins only Coin selection considers only those unconfirmed coins from self, and the same should apply when the user chooses the MAX option without explicitly selecting coins. Unconfirmed coins that are not from self should only be included if the user has selected them explicitly. --- liana-gui/src/app/state/spend/step.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/liana-gui/src/app/state/spend/step.rs b/liana-gui/src/app/state/spend/step.rs index 506214b3..504966e4 100644 --- a/liana-gui/src/app/state/spend/step.rs +++ b/liana-gui/src/app/state/spend/step.rs @@ -283,8 +283,11 @@ impl DefineSpend { } outpoints } else if self.send_max_to_recipient.is_some() { - // If user has not selected coins, send the max available from all coins. - self.coins.iter().map(|(c, _)| c.outpoint).collect() + // If user has not selected coins, send the max available from all owned coins. + self.coins + .iter() + .filter_map(|(c, _)| coin_is_owned(c).then_some(c.outpoint)) + .collect() } else { Vec::new() // pass empty list for auto-selection }; @@ -358,10 +361,9 @@ impl DefineSpend { self.amount_left_to_select = Some(Amount::from_sat(missing)); if !self.is_user_coin_selection { // The missing amount is based on all candidates for coin selection - // being used, which are all coins if there's a recipient with max - // or otherwise all owned coins. + // being used, which are all owned coins. for (coin, selected) in &mut self.coins { - *selected = self.send_max_to_recipient.is_some() || coin_is_owned(coin); + *selected = coin_is_owned(coin); } } if let Some((i, recipient)) = recipient_with_max {