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 f7b97b4..7ce2f50 100644 --- a/src/routes/send.rs +++ b/src/routes/send.rs @@ -1,4 +1,4 @@ -use iced::widget::{column, container, scrollable, text}; +use iced::widget::{column, container, scrollable, text, Checkbox}; use iced::{Color, Length}; use iced::{Element, Padding}; @@ -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] }; container(scrollable(