From 1ca02a48979a7a36440ac57d8d83f988241af92e Mon Sep 17 00:00:00 2001 From: edouardparis Date: Tue, 15 Jul 2025 18:29:45 +0200 Subject: [PATCH] Delete wallet on Liana-Connect --- liana-gui/src/delete.rs | 111 +++++++++++++++++- liana-gui/src/installer/mod.rs | 7 +- liana-gui/src/launcher.rs | 59 +++++++--- .../services/connect/client/backend/mod.rs | 15 +++ liana-gui/src/services/connect/login.rs | 11 +- 5 files changed, 170 insertions(+), 33 deletions(-) diff --git a/liana-gui/src/delete.rs b/liana-gui/src/delete.rs index dcd6b285..1caf53d7 100644 --- a/liana-gui/src/delete.rs +++ b/liana-gui/src/delete.rs @@ -1,16 +1,25 @@ +use liana::miniscript::bitcoin::Network; use std::collections::HashSet; use crate::{ - app::settings::{self, SettingsError, WalletId}, + app::settings::{self, SettingsError, WalletSettings}, dir::NetworkDirectory, - services::connect::client::cache::{self, ConnectCacheError}, + services::connect::{ + client::{ + auth::AuthClient, + cache::{self, ConnectCacheError}, + get_service_config, + }, + login::{connect_with_credentials, BackendState}, + }, signer, }; pub enum DeleteError { Io(std::io::Error), Settings(SettingsError), - Connect(ConnectCacheError), + ConnectCache(ConnectCacheError), + Connect(String), } impl std::fmt::Display for DeleteError { @@ -18,6 +27,7 @@ impl std::fmt::Display for DeleteError { match self { Self::Io(e) => write!(f, "{}", e), Self::Settings(e) => write!(f, "{}", e), + Self::ConnectCache(e) => write!(f, "{}", e), Self::Connect(e) => write!(f, "{}", e), } } @@ -37,11 +47,12 @@ fn ignore_not_found(result: std::io::Result) -> std::io::Result> } } -pub async fn delete_wallet( +pub async fn delete_failed_install( network_dir: &NetworkDirectory, - wallet_id: &WalletId, + wallet_id: &settings::WalletId, ) -> Result<(), DeleteError> { let lianad_directory = network_dir.lianad_data_directory(wallet_id); + if !wallet_id.is_legacy() { ignore_not_found(tokio::fs::remove_dir_all(lianad_directory.path()).await)?; } else { @@ -77,7 +88,95 @@ pub async fn delete_wallet( cache::filter_connect_cache(network_dir, &remaining_accounts) .await - .map_err(DeleteError::Connect)?; + .map_err(DeleteError::ConnectCache)?; + + signer::delete_wallet_mnemonics( + network_dir, + &wallet_id.descriptor_checksum, + wallet_id.timestamp, + ) + .map_err(DeleteError::Io)?; + + Ok(()) +} + +pub async fn delete_wallet( + network: Network, + network_dir: &NetworkDirectory, + wallet: &WalletSettings, + delete_liana_connect: bool, +) -> Result<(), DeleteError> { + let wallet_id = wallet.wallet_id(); + let lianad_directory = network_dir.lianad_data_directory(&wallet_id); + + if !wallet_id.is_legacy() { + ignore_not_found(tokio::fs::remove_dir_all(lianad_directory.path()).await)?; + } else { + // if this is a legacy wallet, then it is the only wallet in the network directory. + ignore_not_found(tokio::fs::remove_file(lianad_directory.sqlite_db_file_path()).await)?; + ignore_not_found( + tokio::fs::remove_dir_all(lianad_directory.lianad_watchonly_wallet_path()).await, + )?; + ignore_not_found( + tokio::fs::remove_file(lianad_directory.path().join("daemon.toml")).await, + )?; + } + + if delete_liana_connect { + if let Some(auth) = &wallet.remote_backend_auth { + let service_config = get_service_config(network) + .await + .map_err(|e| DeleteError::Connect(e.to_string()))?; + + let client = AuthClient::new( + service_config.auth_api_url, + service_config.auth_api_public_key, + auth.email.to_string(), + ); + if let BackendState::WalletExists(client, _, _) = connect_with_credentials( + client, + auth.wallet_id.clone(), + service_config.backend_api_url, + network, + network_dir, + ) + .await + .map_err(|e| DeleteError::Connect(e.to_string()))? + { + tracing::info!("Deleting wallet on Liana-Connect {} plateform", network); + client + .delete_wallet() + .await + .map_err(|e| DeleteError::Connect(e.to_string()))?; + } else { + tracing::warn!("Wallet not found on the platform"); + } + } + } + + let mut remaining_accounts = HashSet::::new(); + settings::update_settings_file(network_dir, |mut settings| { + settings + .wallets + .retain(|settings| settings.wallet_id() != wallet_id); + remaining_accounts = settings + .wallets + .iter() + .filter_map(|settings| { + settings + .remote_backend_auth + .as_ref() + .map(|auth| auth.email.clone()) + }) + .collect(); + settings + }) + .await + .map_err(DeleteError::Settings)?; + + cache::filter_connect_cache(network_dir, &remaining_accounts) + .await + .map_err(DeleteError::ConnectCache)?; signer::delete_wallet_mnemonics( network_dir, diff --git a/liana-gui/src/installer/mod.rs b/liana-gui/src/installer/mod.rs index de7bc34e..7598bce7 100644 --- a/liana-gui/src/installer/mod.rs +++ b/liana-gui/src/installer/mod.rs @@ -299,9 +299,10 @@ impl Installer { // In case of failure during install, block the thread to // deleted the data_dir/network directory in order to start clean again. warn!("Installation failed. Cleaning up the network directory."); - if let Err(e) = Handle::current() - .block_on(delete::delete_wallet(&network_directory, &wallet_id)) - { + if let Err(e) = Handle::current().block_on(delete::delete_failed_install( + &network_directory, + &wallet_id, + )) { error!( "Failed to completely clean the network directory (path: '{}'): {}", network_directory.path().to_string_lossy(), diff --git a/liana-gui/src/launcher.rs b/liana-gui/src/launcher.rs index 1355bf8d..bed87363 100644 --- a/liana-gui/src/launcher.rs +++ b/liana-gui/src/launcher.rs @@ -1,6 +1,6 @@ use iced::{ alignment::Horizontal, - widget::{pick_list, scrollable, Button, Space}, + widget::{checkbox, pick_list, scrollable, Button, Space}, Alignment, Length, Subscription, Task, }; @@ -124,6 +124,7 @@ impl Launcher { None }; self.delete_wallet_modal = Some(DeleteWalletModal::new( + self.network, wallet_datadir, wallets[i].clone(), internal_bitcoind, @@ -425,52 +426,66 @@ pub enum DeleteWalletMessage { ShowModal(usize), CloseModal, Confirm(WalletId), + DeleteLianaConnect(bool), Deleted, } struct DeleteWalletModal { + network: Network, network_directory: NetworkDirectory, wallet_settings: WalletSettings, warning: Option, deleted: bool, + delete_liana_connect: bool, // `None` means we were not able to determine whether wallet uses internal bitcoind. internal_bitcoind: Option, } impl DeleteWalletModal { fn new( + network: Network, network_directory: NetworkDirectory, wallet_settings: WalletSettings, internal_bitcoind: Option, ) -> Self { Self { + network, wallet_settings, network_directory, warning: None, deleted: false, + delete_liana_connect: false, internal_bitcoind, } } fn update(&mut self, message: Message) -> Task { - if let Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm(wallet_id))) = - message - { - if wallet_id != self.wallet_settings.wallet_id() { - return Task::none(); + match message { + Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm(wallet_id))) => { + if wallet_id != self.wallet_settings.wallet_id() { + return Task::none(); + } + self.warning = None; + if let Err(e) = Handle::current().block_on(delete_wallet( + self.network, + &self.network_directory, + &self.wallet_settings, + self.delete_liana_connect, + )) { + self.warning = Some(e); + } else { + self.deleted = true; + return Task::perform(async {}, |_| { + Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) + }); + }; } - self.warning = None; - if let Err(e) = Handle::current().block_on(delete_wallet( - &self.network_directory, - &self.wallet_settings.wallet_id(), - )) { - self.warning = Some(e); - } else { - self.deleted = true; - return Task::perform(async {}, |_| { - Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) - }); - }; + Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::DeleteLianaConnect( + delete, + ))) => { + self.delete_liana_connect = delete; + } + _ => {} } Task::none() } @@ -523,6 +538,14 @@ impl DeleteWalletModal { .style(theme::text::destructive) .width(Length::Fill), )) + .push_maybe(self.wallet_settings.remote_backend_auth.as_ref().map(|_| { + checkbox("Delete also on Liana-Connect", self.delete_liana_connect) + .on_toggle(|v| { + ViewMessage::DeleteWallet(DeleteWalletMessage::DeleteLianaConnect( + v, + )) + }) + })) .push(Row::new().push(text(help_text_1))) .push_maybe( help_text_2 diff --git a/liana-gui/src/services/connect/client/backend/mod.rs b/liana-gui/src/services/connect/client/backend/mod.rs index 115e4e74..6e6e9d39 100644 --- a/liana-gui/src/services/connect/client/backend/mod.rs +++ b/liana-gui/src/services/connect/client/backend/mod.rs @@ -471,6 +471,21 @@ impl BackendWalletClient { pub async fn auth(&self) -> AccessTokenResponse { self.inner.auth.read().await.clone() } + + pub async fn delete_wallet(&self) -> Result<(), DaemonError> { + self.inner + .request( + Method::DELETE, + &format!("{}/v1/wallets/{}", self.inner.url, self.wallet_uuid), + ) + .await + .send() + .await? + .check_success() + .await?; + + Ok(()) + } } #[async_trait] diff --git a/liana-gui/src/services/connect/login.rs b/liana-gui/src/services/connect/login.rs index e4c95292..25839815 100644 --- a/liana-gui/src/services/connect/login.rs +++ b/liana-gui/src/services/connect/login.rs @@ -16,7 +16,7 @@ use crate::{ settings::{SettingsError, WalletSettings}, }, daemon::DaemonError, - dir::LianaDirectory, + dir::{LianaDirectory, NetworkDirectory}, }; use super::client::{ @@ -170,7 +170,7 @@ impl LianaLiteLogin { auth.wallet_id, service_config.backend_api_url, network, - datadir, + &datadir.network_directory(network), ) .await }, @@ -483,10 +483,9 @@ pub async fn connect_with_credentials( wallet_id: String, backend_api_url: String, network: Network, - liana_directory: LianaDirectory, + network_dir: &NetworkDirectory, ) -> Result { - let network_dir = liana_directory.network_directory(network); - let mut tokens = cache::Account::from_cache(&network_dir, &auth.email) + let mut tokens = cache::Account::from_cache(network_dir, &auth.email) .map_err(|e| match e { ConnectCacheError::NotFound => Error::CredentialsMissing, _ => e.into(), @@ -495,7 +494,7 @@ pub async fn connect_with_credentials( .tokens; if tokens.expires_at < chrono::Utc::now().timestamp() { - tokens = cache::update_connect_cache(&network_dir, &tokens, &auth, true).await?; + tokens = cache::update_connect_cache(network_dir, &tokens, &auth, true).await?; } let client = BackendClient::connect(auth, backend_api_url, tokens, network).await?;