From 7d54d2479e18b8e018b782e66dc16b958053e554 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Thu, 16 May 2024 17:05:44 -0500 Subject: [PATCH 1/2] Send max on-chain --- src/bridge.rs | 7 +++++-- src/core.rs | 32 +++++++++++++++++++++++++------- src/main.rs | 18 +++++++++++++++--- src/routes/send.rs | 7 +++++-- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/bridge.rs b/src/bridge.rs index b9959d5..87f6712 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -10,7 +10,10 @@ use tokio::sync::mpsc; pub enum UICoreMsg { SendLightning(Bolt11Invoice), ReceiveLightning(Amount), - SendOnChain { address: Address, amount_sats: u64 }, + SendOnChain { + address: Address, + amount_sats: Option, + }, ReceiveOnChain, GetFederationInfo(InviteCode), AddFederation(InviteCode), @@ -71,7 +74,7 @@ impl UIHandle { self.msg_send(UICoreMsg::SendLightning(invoice)).await; } - pub async fn send_onchain(&self, address: Address, amount_sats: u64) { + pub async fn send_onchain(&self, address: Address, amount_sats: Option) { self.msg_send(UICoreMsg::SendOnChain { address, amount_sats, diff --git a/src/core.rs b/src/core.rs index 231e83f..5ad1a51 100644 --- a/src/core.rs +++ b/src/core.rs @@ -191,19 +191,37 @@ impl HarborCore { Ok(invoice) } - async fn send_onchain(&self, address: Address, sats: u64) -> anyhow::Result<()> { + /// Sends a given amount of sats to a given address, if the amount is None, send all funds + async fn send_onchain(&self, address: Address, sats: Option) -> anyhow::Result<()> { // todo go through all clients and select the first one that has enough balance let client = self.get_client().await.fedimint_client; let onchain = client.get_first_module::(); - let amount = bitcoin::Amount::from_sat(sats); - // todo add manual fee selection - let fees = onchain.get_withdraw_fees(address.clone(), amount).await?; + let (fees, amount) = match sats { + Some(sats) => { + let amount = bitcoin::Amount::from_sat(sats); + let fees = onchain.get_withdraw_fees(address.clone(), amount).await?; + (fees, amount) + } + None => { + let balance = client.get_balance().await; + // get fees for the entire balance + let fees = onchain + .get_withdraw_fees( + address.clone(), + bitcoin::Amount::from_sat(balance.sats_round_down()), + ) + .await?; + + let fees_paid = Amount::from_sats(fees.amount().to_sat()); + let amount = balance - fees_paid; + + (fees, bitcoin::Amount::from_sat(amount.sats_round_down())) + } + }; - let op_id = onchain - .withdraw(address.clone(), bitcoin::Amount::from_sat(sats), fees, ()) - .await?; + let op_id = onchain.withdraw(address.clone(), amount, fees, ()).await?; self.storage.create_onchain_payment( op_id, diff --git a/src/main.rs b/src/main.rs index 4ffd7f8..d7894cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,6 +86,7 @@ pub enum Message { ReceiveStateReset, SendDestInputChanged(String), SendAmountInputChanged(String), + SetIsMax(bool), SendStateReset, PasswordInputChanged(String), MintInviteCodeInputChanged(String), @@ -117,6 +118,7 @@ pub struct HarborWallet { send_success_msg: Option, send_dest_input_str: String, send_amount_input_str: String, + is_max: bool, password_input_str: String, unlock_status: UnlockStatus, unlock_failure_reason: Option, @@ -156,7 +158,7 @@ impl HarborWallet { async fn async_send_onchain( ui_handle: Option>, address: Address, - amount_sats: u64, + amount_sats: Option, ) { println!("Got to async_send"); if let Some(ui_handle) = ui_handle { @@ -236,6 +238,10 @@ impl HarborWallet { self.send_amount_input_str = input; Command::none() } + Message::SetIsMax(is_max) => { + self.is_max = is_max; + Command::none() + } Message::PasswordInputChanged(input) => { self.password_input_str = input; Command::none() @@ -253,6 +259,7 @@ impl HarborWallet { self.send_success_msg = None; self.send_dest_input_str = String::new(); self.send_amount_input_str = String::new(); + self.is_max = false; self.send_status = SendStatus::Idle; Command::none() } @@ -282,7 +289,12 @@ impl HarborWallet { |_| Message::Noop, ) } else if let Ok(address) = Address::from_str(&invoice_str) { - let amount = self.send_amount_input_str.parse::().unwrap(); // TODO: error handling + let amount = if self.is_max { + None + } else { + // TODO: error handling + Some(self.send_amount_input_str.parse::().unwrap()) + }; Command::perform( Self::async_send_onchain(self.ui_handle.clone(), address, amount), |_| Message::Noop, @@ -326,7 +338,7 @@ impl HarborWallet { let address = Address::from_str(hardcoded_donation_address).unwrap(); Command::perform( - Self::async_send_onchain(self.ui_handle.clone(), address, amount), + Self::async_send_onchain(self.ui_handle.clone(), address, Some(amount)), |_| Message::Noop, ) } diff --git a/src/routes/send.rs b/src/routes/send.rs index cdc166a..65fa35c 100644 --- a/src/routes/send.rs +++ b/src/routes/send.rs @@ -1,4 +1,4 @@ -use iced::widget::{column, text}; +use iced::widget::{column, text, Checkbox}; use iced::Color; use iced::Element; @@ -8,6 +8,7 @@ use crate::{HarborWallet, Message, SendStatus}; pub fn send(harbor: &HarborWallet) -> Element { let header = h_header("Send", "Send to an on-chain address or lightning invoice."); + // todo disable amount input if max is selected let amount_input = h_input( "Amount", "420", @@ -48,6 +49,8 @@ pub fn send(harbor: &HarborWallet) -> Element { .color(Color::from_rgb(0., 255., 0.)) }); + let checkbox = Checkbox::new("Send Max", harbor.is_max).on_toggle(Message::SetIsMax); + let column = if let Some(failure_message) = failure_message { let dangit_button = h_button("Dangit", SvgIcon::Squirrel, false).on_press(Message::SendStateReset); @@ -56,7 +59,7 @@ pub fn send(harbor: &HarborWallet) -> Element { let nice_button = h_button("Nice", SvgIcon::Heart, false).on_press(Message::SendStateReset); column![header, success_message, nice_button] } else { - column![header, amount_input, dest_input, send_button] + column![header, amount_input, checkbox, dest_input, send_button] }; column![ From dbbca0600d20ef79e2445f2ddf2d9c78c21e8245 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Thu, 16 May 2024 17:41:58 -0500 Subject: [PATCH 2/2] Handle underflow --- src/core.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/core.rs b/src/core.rs index 5ad1a51..73716a7 100644 --- a/src/core.rs +++ b/src/core.rs @@ -206,6 +206,11 @@ impl HarborCore { } None => { let balance = client.get_balance().await; + + if balance.sats_round_down() == 0 { + return Err(anyhow!("No funds in wallet")); + } + // get fees for the entire balance let fees = onchain .get_withdraw_fees( @@ -215,7 +220,11 @@ impl HarborCore { .await?; let fees_paid = Amount::from_sats(fees.amount().to_sat()); - let amount = balance - fees_paid; + let amount = balance.saturating_sub(fees_paid); + + if amount.sats_round_down() < 546 { + return Err(anyhow!("Not enough funds to send")); + } (fees, bitcoin::Amount::from_sat(amount.sats_round_down())) }