From 68bb742755b3a277aa91b15f540ab6d8012e1384 Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Wed, 5 Mar 2025 06:07:51 +0100 Subject: [PATCH] installer: add wallet export feature at descriptor backup step --- liana-gui/src/app/state/export.rs | 10 ++- liana-gui/src/app/state/settings/mod.rs | 6 +- liana-gui/src/app/state/settings/wallet.rs | 6 +- liana-gui/src/app/view/message.rs | 4 +- liana-gui/src/backup.rs | 6 +- liana-gui/src/installer/message.rs | 5 +- liana-gui/src/installer/mod.rs | 3 + .../src/installer/step/descriptor/mod.rs | 73 +++++++++++++++++-- liana-gui/src/installer/view/mod.rs | 17 ++++- 9 files changed, 108 insertions(+), 22 deletions(-) diff --git a/liana-gui/src/app/state/export.rs b/liana-gui/src/app/state/export.rs index 5e8a01c3..e6f3ac0c 100644 --- a/liana-gui/src/app/state/export.rs +++ b/liana-gui/src/app/state/export.rs @@ -69,7 +69,7 @@ impl ExportModal { } ImportExportType::ImportPsbt => "psbt.psbt".into(), ImportExportType::ImportDescriptor => "descriptor.descriptor".into(), - ImportExportType::ExportLabels => format!("liana-labels-{date}.csv"), + ImportExportType::ExportLabels => format!("liana-labels-{date}.jsonl"), ImportExportType::ExportBackup(_) => format!("liana-backup-{date}.json"), } } @@ -95,8 +95,12 @@ impl ExportModal { self.state = ImportExportState::Progress(p); } } - Progress::Finished | Progress::Ended => self.state = ImportExportState::Ended, - Progress::Error(e) => self.error = Some(e), + Progress::Finished | Progress::Ended => { + self.state = ImportExportState::Ended; + } + Progress::Error(e) => { + self.error = Some(e); + } Progress::None => {} Progress::Psbt(_) => { if self.import_export_type == ImportExportType::ImportPsbt { diff --git a/liana-gui/src/app/state/settings/mod.rs b/liana-gui/src/app/state/settings/mod.rs index 69b3117d..faede757 100644 --- a/liana-gui/src/app/state/settings/mod.rs +++ b/liana-gui/src/app/state/settings/mod.rs @@ -271,13 +271,15 @@ impl State for ImportExportSettingsState { }, |s| { Message::View(view::Message::Settings( - view::SettingsMessage::ExportBackup(s), + view::SettingsMessage::ExportBackup(Ok(s)), )) }, ); } } - Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(backup))) => { + Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(Ok( + backup, + )))) => { let modal = ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup)); launch!(self, modal); } diff --git a/liana-gui/src/app/state/settings/wallet.rs b/liana-gui/src/app/state/settings/wallet.rs index 1315f373..917f764c 100644 --- a/liana-gui/src/app/state/settings/wallet.rs +++ b/liana-gui/src/app/state/settings/wallet.rs @@ -240,7 +240,7 @@ impl State for WalletSettingsState { }, |s| { Message::View(view::Message::Settings( - view::SettingsMessage::ExportBackup(s), + view::SettingsMessage::ExportBackup(Ok(s)), )) }, ) @@ -248,7 +248,9 @@ impl State for WalletSettingsState { Task::none() } } - Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(backup))) => { + Message::View(view::Message::Settings(view::SettingsMessage::ExportBackup(Ok( + backup, + )))) => { if self.modal.is_none() { let modal = // ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup)); diff --git a/liana-gui/src/app/view/message.rs b/liana-gui/src/app/view/message.rs index 6ff27a6c..e5c46436 100644 --- a/liana-gui/src/app/view/message.rs +++ b/liana-gui/src/app/view/message.rs @@ -1,4 +1,4 @@ -use crate::{app::menu::Menu, export::ImportExportMessage, node::bitcoind::RpcAuthType}; +use crate::{app::menu::Menu, backup, export::ImportExportMessage, node::bitcoind::RpcAuthType}; use liana::miniscript::bitcoin::{bip32::Fingerprint, OutPoint}; pub trait Close { @@ -89,7 +89,7 @@ pub enum SettingsMessage { ExportTransactions, ExportLabels, ExportWallet, - ExportBackup(String), + ExportBackup(Result), ImportWallet, AboutSection, RegisterWallet, diff --git a/liana-gui/src/backup.rs b/liana-gui/src/backup.rs index 86c4398d..2c563f9c 100644 --- a/liana-gui/src/backup.rs +++ b/liana-gui/src/backup.rs @@ -46,19 +46,19 @@ pub struct Backup { pub proprietary: serde_json::Map, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Error { DescriptorMissing, NotSingleWallet, Json, SettingsFromFile, - Daemon(DaemonError), + Daemon(String), TxTimeMissing, } impl From for Error { fn from(value: DaemonError) -> Self { - Error::Daemon(value) + Error::Daemon(value.to_string()) } } diff --git a/liana-gui/src/installer/message.rs b/liana-gui/src/installer/message.rs index 414532ac..81ebc225 100644 --- a/liana-gui/src/installer/message.rs +++ b/liana-gui/src/installer/message.rs @@ -8,6 +8,7 @@ use super::{context, Error}; use crate::{ app::settings::ProviderKey, app::view::Close, + backup, download::{DownloadError, Progress}, export::ImportExportMessage, hw::HardwareWalletMessage, @@ -51,6 +52,8 @@ pub enum Message { RedeemNextKey, KeyRedeemed(ProviderKey, Result<(), services::Error>), AllKeysRedeemed, + BackupWallet, + ExportWallet(Result), ImportExport(ImportExportMessage), } @@ -62,7 +65,7 @@ impl Close for Message { impl From for Message { fn from(value: ImportExportMessage) -> Self { - Self::ImportExport(value) + Message::ImportExport(value) } } diff --git a/liana-gui/src/installer/mod.rs b/liana-gui/src/installer/mod.rs index 4c946087..4c0a6144 100644 --- a/liana-gui/src/installer/mod.rs +++ b/liana-gui/src/installer/mod.rs @@ -25,6 +25,7 @@ use crate::{ settings::{self as gui_settings, AuthConfig, Settings, SettingsError, WalletSetting}, wallet::wallet_name, }, + backup, daemon::DaemonError, datadir::create_directory, hw::{HardwareWalletConfig, HardwareWallets}, @@ -730,6 +731,7 @@ pub enum Error { CannotGetAvailablePort(String), Unexpected(String), HardwareWallet(async_hwi::Error), + Backup(backup::Error), } impl From for Error { @@ -783,6 +785,7 @@ impl std::fmt::Display for Error { Self::CannotCreateFile(e) => write!(f, "Failed to create file: {}", e), Self::Unexpected(e) => write!(f, "Unexpected: {}", e), Self::HardwareWallet(e) => write!(f, "Hardware Wallet: {}", e), + Self::Backup(e) => write!(f, "Backup: {:?}", e), } } } diff --git a/liana-gui/src/installer/step/descriptor/mod.rs b/liana-gui/src/installer/step/descriptor/mod.rs index e98463b9..0bf6d31e 100644 --- a/liana-gui/src/installer/step/descriptor/mod.rs +++ b/liana-gui/src/installer/step/descriptor/mod.rs @@ -16,7 +16,9 @@ use liana_ui::{component::form, widget::Element}; use async_hwi::DeviceKind; use crate::{ - app::{settings::KeySetting, wallet::wallet_name}, + app::{settings::KeySetting, state::export::ExportModal, wallet::wallet_name}, + backup::{self, Backup}, + export::{ImportExportMessage, ImportExportType}, hw::{HardwareWallet, HardwareWallets}, installer::{ message::{self, Message}, @@ -292,16 +294,71 @@ pub struct BackupDescriptor { done: bool, descriptor: Option, keys: HashMap, + modal: Option, + error: Option, + context: Option, } impl Step for BackupDescriptor { + fn subscription(&self, _hws: &HardwareWallets) -> Subscription { + if let Some(modal) = &self.modal { + if let Some(sub) = modal.subscription() { + sub.map(|m| Message::ImportExport(ImportExportMessage::Progress(m))) + } else { + Subscription::none() + } + } else { + Subscription::none() + } + } fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Task { - if let Message::UserActionDone(done) = message { - self.done = done; + match message { + Message::ImportExport(ImportExportMessage::Close) => { + self.modal = None; + } + Message::ImportExport(m) => { + if let Some(modal) = self.modal.as_mut() { + let task: Task = modal.update(m); + return task; + }; + } + Message::BackupWallet => { + if let (None, Some(ctx)) = (&self.modal, self.context.as_ref()) { + let ctx = ctx.clone(); + return Task::perform( + async move { + let backup = Backup::from_installer(ctx, true).await?; + serde_json::to_string_pretty(&backup).map_err(|_| backup::Error::Json) + }, + Message::ExportWallet, + ); + } + } + Message::ExportWallet(str) => { + if self.modal.is_none() { + let str = match str { + Ok(s) => s, + Err(e) => { + tracing::error!("{e:?}"); + self.error = Some(Error::Backup(e)); + return Task::none(); + } + }; + let modal = ExportModal::new(None, ImportExportType::ExportBackup(str)); + let launch = modal.launch(); + self.modal = Some(modal); + return launch; + } + } + Message::UserActionDone(done) => { + self.done = done; + } + _ => {} } Task::none() } fn load_context(&mut self, ctx: &Context) { + self.context = Some(ctx.clone()); if self.descriptor != ctx.descriptor { self.descriptor.clone_from(&ctx.descriptor); self.done = false; @@ -318,13 +375,19 @@ impl Step for BackupDescriptor { progress: (usize, usize), email: Option<&'a str>, ) -> Element { - view::backup_descriptor( + let content = view::backup_descriptor( progress, email, self.descriptor.as_ref().expect("Must be a descriptor"), &self.keys, + self.error.as_ref(), self.done, - ) + ); + if let Some(modal) = &self.modal { + modal.view(content) + } else { + content + } } } diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs index c85a542e..733df5b9 100644 --- a/liana-gui/src/installer/view/mod.rs +++ b/liana-gui/src/installer/view/mod.rs @@ -689,6 +689,7 @@ pub fn backup_descriptor<'a>( email: Option<&'a str>, descriptor: &'a LianaDescriptor, keys: &'a HashMap, + error: Option<&Error>, done: bool, ) -> Element<'a, Message> { layout( @@ -724,6 +725,7 @@ pub fn backup_descriptor<'a>( )) .max_width(1000), ) + .push_maybe(error.map(|e| card::error("Failed to export backup", e.to_string()))) .push( card::simple( Column::new() @@ -741,10 +743,17 @@ pub fn backup_descriptor<'a>( ), ) .push( - Row::new().push(Column::new().width(Length::Fill)).push( - button::secondary(Some(icon::clipboard_icon()), "Copy") - .on_press(Message::Clibpboard(descriptor.to_string())), - ), + Row::new() + .push(Space::with_width(Length::Fill)) + .push( + button::secondary(Some(icon::wallet_icon()), "Backup") + .on_press(Message::BackupWallet), + ) + .push(Space::with_width(10)) + .push( + button::secondary(Some(icon::clipboard_icon()), "Copy") + .on_press(Message::Clibpboard(descriptor.to_string())), + ), ) .spacing(10), )