diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs index c06e4b84..bffe8e7c 100644 --- a/gui/src/app/message.rs +++ b/gui/src/app/message.rs @@ -23,6 +23,7 @@ pub enum Message { Coins(Result, Error>), SpendTxs(Result, Error>), Psbt(Result), + Recovery(Result), Signed(Result<(Psbt, Fingerprint), Error>), Updated(Result<(), Error>), Saved(Result<(), Error>), diff --git a/gui/src/app/state/recovery.rs b/gui/src/app/state/recovery.rs index cdfadef9..3dcc2c5f 100644 --- a/gui/src/app/state/recovery.rs +++ b/gui/src/app/state/recovery.rs @@ -10,19 +10,19 @@ use crate::{ error::Error, menu::Menu, message::Message, + state::spend::detail, state::{redirect, State}, view, wallet::Wallet, }, daemon::{ - model::{remaining_sequence, Coin}, + model::{remaining_sequence, Coin, SpendTx}, Daemon, }, - hw::{list_hardware_wallets, HardwareWallet}, ui::component::form, }; -use liana::miniscript::bitcoin::{util::psbt::Psbt, Address, Amount, Network}; +use liana::miniscript::bitcoin::{Address, Amount, Network}; pub struct RecoveryPanel { wallet: Wallet, @@ -32,10 +32,7 @@ pub struct RecoveryPanel { warning: Option, feerate: form::Value, recipient: form::Value, - generated: Option, - hws: Vec, - selected_hw: Option, - signed: bool, + generated: Option, /// timelock value to pass for the heir to consume a coin. timelock: u32, } @@ -72,30 +69,27 @@ impl RecoveryPanel { recipient: form::Value::default(), generated: None, timelock, - hws: Vec::new(), - selected_hw: None, - signed: false, } } } impl State for RecoveryPanel { - fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> { - view::modal( - false, - self.warning.as_ref(), - view::recovery::recovery( - &self.locked_coins, - &self.recoverable_coins, - &self.feerate, - &self.recipient, - self.generated.as_ref(), - &self.hws, - self.selected_hw, - self.signed, - ), - None::>, - ) + fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> { + if let Some(generated) = &self.generated { + generated.view(cache) + } else { + view::modal( + false, + self.warning.as_ref(), + view::recovery::recovery( + &self.locked_coins, + &self.recoverable_coins, + &self.feerate, + &self.recipient, + ), + None::>, + ) + } } fn update( @@ -127,27 +121,18 @@ impl State for RecoveryPanel { } } }, - // We add the new hws without dropping the reference of the previous ones. - Message::ConnectedHardwareWallets(hws) => { - for h in hws { - if !self.hws.iter().any(|hw| hw.fingerprint == h.fingerprint) { - self.hws.push(h); - } + Message::Recovery(res) => match res { + Ok(tx) => { + self.generated = Some(detail::SpendTxState::new( + self.wallet.clone(), + self.config.clone(), + tx, + false, + )) } - } - Message::Psbt(res) => match res { - Ok(psbt) => self.generated = Some(psbt), Err(e) => self.warning = Some(e), }, - Message::Updated(res) => match res { - Err(e) => self.warning = Some(e), - Ok(()) => { - self.warning = None; - self.signed = true; - } - }, Message::View(msg) => match msg { - view::Message::Reload => return self.load(daemon), view::Message::Close => return redirect(Menu::Settings), view::Message::Previous => self.generated = None, view::Message::CreateSpend(view::CreateSpendMessage::RecipientEdited( @@ -177,68 +162,52 @@ impl State for RecoveryPanel { self.warning = None; return Command::perform( async move { - daemon - .create_recovery(address, feerate_vb) - .map_err(|e| e.into()) + let psbt = daemon.create_recovery(address, feerate_vb)?; + let coins = daemon.list_coins().map(|res| res.coins)?; + let coins = coins + .iter() + .filter(|coin| { + psbt.unsigned_tx + .input + .iter() + .any(|input| input.previous_output == coin.outpoint) + }) + .copied() + .collect(); + Ok(SpendTx::new(psbt, coins)) }, - Message::Psbt, + Message::Recovery, ); } - view::Message::Spend(view::SpendTxMessage::SelectHardwareWallet(i)) => { - if let Some(hw) = self.hws.get(i) { - let device = hw.device.clone(); - self.selected_hw = Some(i); - let psbt = self.generated.clone().unwrap(); - return Command::perform( - send_funds(daemon, device, psbt), - Message::Updated, - ); + _ => { + if let Some(generated) = &mut self.generated { + return generated.update(daemon, cache, Message::View(msg)); } } - _ => {} }, - _ => {} + _ => { + if let Some(generated) = &mut self.generated { + return generated.update(daemon, cache, message); + } + } }; Command::none() } fn load(&self, daemon: Arc) -> Command { - let config = self.config.clone(); - let desc = self.wallet.main_descriptor.to_string(); let daemon = daemon.clone(); - Command::batch(vec![ - Command::perform( - async move { - daemon - .list_coins() - .map(|res| res.coins) - .map_err(|e| e.into()) - }, - Message::Coins, - ), - Command::perform( - list_hws(config, self.wallet.name.clone(), desc), - Message::ConnectedHardwareWallets, - ), - ]) + Command::perform( + async move { + daemon + .list_coins() + .map(|res| res.coins) + .map_err(|e| e.into()) + }, + Message::Coins, + ) } } -async fn list_hws(config: Config, wallet_name: String, descriptor: String) -> Vec { - list_hardware_wallets(&config.hardware_wallets, Some((&wallet_name, &descriptor))).await -} - -async fn send_funds( - daemon: Arc, - hw: std::sync::Arc, - mut psbt: Psbt, -) -> Result<(), Error> { - hw.sign_tx(&mut psbt).await.map_err(Error::from)?; - daemon.update_spend_tx(&psbt)?; - daemon.broadcast_spend_tx(&psbt.unsigned_tx.txid())?; - Ok(()) -} - impl From for Box { fn from(s: RecoveryPanel) -> Box { Box::new(s) diff --git a/gui/src/app/state/spend/mod.rs b/gui/src/app/state/spend/mod.rs index ddc4368e..84dad9d0 100644 --- a/gui/src/app/state/spend/mod.rs +++ b/gui/src/app/state/spend/mod.rs @@ -1,4 +1,4 @@ -mod detail; +pub mod detail; mod step; use std::sync::Arc; diff --git a/gui/src/app/view/recovery.rs b/gui/src/app/view/recovery.rs index 15b14c73..0fd90b27 100644 --- a/gui/src/app/view/recovery.rs +++ b/gui/src/app/view/recovery.rs @@ -1,18 +1,14 @@ use iced::{ - widget::{Button, Column, Container, Row, Space}, + widget::{Column, Container, Row, Space}, Alignment, Element, Length, }; -use liana::miniscript::bitcoin::{util::psbt::Psbt, Amount}; +use liana::miniscript::bitcoin::Amount; use crate::{ - app::view::{ - hw::hw_list_view, - message::{CreateSpendMessage, Message}, - }, - hw::HardwareWallet, + app::view::message::{CreateSpendMessage, Message}, ui::{ - component::{button, card, form, text::*}, + component::{button, form, text::*}, icon, util::Collection, }, @@ -24,10 +20,6 @@ pub fn recovery<'a>( recoverable_coins: &(usize, Amount), feerate: &form::Value, address: &'a form::Value, - generated: Option<&Psbt>, - hws: &[HardwareWallet], - chosen_hw: Option, - done: bool, ) -> Element<'a, Message> { Column::new() .push(Space::with_height(Length::Units(100))) @@ -59,173 +51,7 @@ pub fn recovery<'a>( None }) .push(Space::with_height(Length::Units(20))) - .push(if let Some(psbt) = generated { - if done { - Column::new() - .spacing(20) - .align_items(Alignment::Center) - .push(text("Funds were sweeped")) - .push(card::simple( - Column::new() - .push( - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push( - text(format!( - "{}", - Amount::from_sat(psbt.unsigned_tx.output[0].value) - )) - .small() - .bold(), - ) - .push(text(" to ").small()) - .push(text(&address.value).small().bold()), - ) - .push( - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push( - text(format!("Txid: {}", psbt.unsigned_tx.txid())).small(), - ) - .push( - Button::new(icon::clipboard_icon().small()) - .on_press(Message::Clipboard( - psbt.unsigned_tx.txid().to_string(), - )) - .style(button::Style::Border.into()), - ), - ) - .push_maybe( - if recoverable_coins.1.to_sat() > psbt.unsigned_tx.output[0].value { - Some( - Row::new().push( - text(format!( - "Fees: {}", - recoverable_coins.1 - - Amount::from_sat( - psbt.unsigned_tx.output[0].value - ) - )) - .small(), - ), - ) - } else { - None - }, - ), - )) - } else { - Column::new() - .spacing(20) - .align_items(Alignment::Center) - .push_maybe(if chosen_hw.is_none() { - Some(button::border(None, "< Previous").on_press(Message::Previous)) - } else { - None - }) - .push(text("Sign the transaction to sweep the funds").bold()) - .push(card::simple( - Column::new() - .push( - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push( - text(format!( - "{}", - Amount::from_sat(psbt.unsigned_tx.output[0].value) - )) - .small() - .bold(), - ) - .push(text(" to ").small()) - .push(text(&address.value).small().bold()), - ) - .push( - Row::new() - .spacing(5) - .align_items(Alignment::Center) - .push( - text(format!("Txid: {}", psbt.unsigned_tx.txid())).small(), - ) - .push( - Button::new(icon::clipboard_icon().small()) - .on_press(Message::Clipboard( - psbt.unsigned_tx.txid().to_string(), - )) - .style(button::Style::Border.into()), - ), - ) - .push_maybe( - if recoverable_coins.1.to_sat() > psbt.unsigned_tx.output[0].value { - Some( - Row::new().push( - text(format!( - "Fees: {}", - recoverable_coins.1 - - Amount::from_sat( - psbt.unsigned_tx.output[0].value - ) - )) - .small(), - ), - ) - } else { - None - }, - ), - )) - .push(if !hws.is_empty() { - Column::new() - .push( - Row::new() - .align_items(Alignment::Center) - .push( - text("Select hardware wallet to sign with:") - .bold() - .width(Length::Fill), - ) - .push_maybe(if chosen_hw.is_none() { - Some( - button::border(None, "Refresh") - .on_press(Message::Reload), - ) - } else { - None - }), - ) - .spacing(10) - .push(hws.iter().enumerate().fold( - Column::new().spacing(10), - |col, (i, hw)| { - col.push(hw_list_view( - i, - hw, - Some(i) == chosen_hw, - chosen_hw.is_some(), - false, - )) - }, - )) - .max_width(500) - } else { - Column::new() - .push( - Column::new() - .spacing(20) - .width(Length::Fill) - .push("Please connect a hardware wallet") - .push( - button::primary(None, "Refresh").on_press(Message::Reload), - ) - .align_items(Alignment::Center), - ) - .width(Length::Fill) - }) - } - } else { + .push( Column::new() .push(text("Enter destination address and feerate:").bold()) .push( @@ -267,8 +93,8 @@ pub fn recovery<'a>( }, ) .spacing(20) - .align_items(Alignment::Center) - }) + .align_items(Alignment::Center), + ) .align_items(Alignment::Center) .spacing(20) .into()