From 381421038b0e12ed257679b31a16102021d6d76a Mon Sep 17 00:00:00 2001 From: edouardparis Date: Mon, 19 May 2025 16:14:05 +0200 Subject: [PATCH 1/4] Add wallet alias to settings --- liana-gui/src/app/settings.rs | 1 + liana-gui/src/app/state/settings/wallet.rs | 69 +++++++++++++++++-- liana-gui/src/app/view/message.rs | 1 + liana-gui/src/app/view/settings.rs | 13 +++- liana-gui/src/app/wallet.rs | 8 +++ liana-gui/src/daemon/mod.rs | 1 + liana-gui/src/installer/mod.rs | 7 +- liana-gui/src/installer/step/backend.rs | 5 +- liana-gui/src/installer/view/mod.rs | 19 +++-- liana-gui/src/launcher.rs | 24 ++++--- liana-gui/src/main.rs | 14 ++-- .../services/connect/client/backend/api.rs | 2 + .../services/connect/client/backend/mod.rs | 53 ++++++++------ liana-gui/src/services/connect/login.rs | 15 ++-- 14 files changed, 174 insertions(+), 58 deletions(-) diff --git a/liana-gui/src/app/settings.rs b/liana-gui/src/app/settings.rs index cca444fb..303d77f2 100644 --- a/liana-gui/src/app/settings.rs +++ b/liana-gui/src/app/settings.rs @@ -132,6 +132,7 @@ impl AuthConfig { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct WalletSettings { pub name: String, + pub alias: Option, pub descriptor_checksum: String, pub pinned_at: Option, // if wallet is using remote backend, then this information is stored on the remote backend diff --git a/liana-gui/src/app/state/settings/wallet.rs b/liana-gui/src/app/state/settings/wallet.rs index 8d24ac6b..8d15f42e 100644 --- a/liana-gui/src/app/state/settings/wallet.rs +++ b/liana-gui/src/app/state/settings/wallet.rs @@ -49,6 +49,7 @@ pub struct WalletSettingsState { descriptor: LianaDescriptor, keys_aliases: Vec<(Fingerprint, form::Value)>, wallet: Arc, + wallet_alias: form::Value, modal: Modal, processing: bool, updated: bool, @@ -61,6 +62,10 @@ impl WalletSettingsState { data_dir, descriptor: wallet.main_descriptor.clone(), keys_aliases: Self::keys_aliases(&wallet), + wallet_alias: form::Value { + value: wallet.alias.clone().unwrap_or_default(), + valid: true, + }, wallet, warning: None, modal: Modal::None, @@ -103,6 +108,7 @@ impl State for WalletSettingsState { cache, self.warning.as_ref(), &self.descriptor, + &self.wallet_alias, &self.keys_aliases, &self.wallet.provider_keys, self.processing, @@ -159,6 +165,13 @@ impl State for WalletSettingsState { Task::none() } } + Message::View(view::Message::Settings(view::SettingsMessage::WalletAliasEdited( + alias, + ))) => { + self.wallet_alias.valid = alias.len() < 64; + self.wallet_alias.value = alias; + Task::none() + } Message::View(view::Message::Settings( view::SettingsMessage::FingerprintAliasEdited(fg, value), )) => { @@ -176,10 +189,26 @@ impl State for WalletSettingsState { self.processing = true; self.updated = false; Task::perform( - update_keys_aliases( + update_aliases( self.data_dir.clone(), cache.network, self.wallet.clone(), + match self + .wallet + .alias + .as_ref() + .map(|a| *a == self.wallet_alias.value) + { + Some(true) => None, + Some(false) => Some(self.wallet_alias.value.clone()), + None => { + if self.wallet_alias.value.is_empty() { + None + } else { + Some(self.wallet_alias.value.clone()) + } + } + }, self.keys_aliases .iter() .map(|(fg, name)| (*fg, name.value.to_owned())) @@ -208,10 +237,11 @@ impl State for WalletSettingsState { self.processing = true; self.updated = false; Task::perform( - update_keys_aliases( + update_aliases( self.data_dir.clone(), cache.network, self.wallet.clone(), + None, aliases.into_iter().map(|(fg, ks)| (fg, ks.name)).collect(), daemon, ), @@ -462,7 +492,7 @@ async fn register_wallet( wallet.hardware_wallets.push(hw_cfg) } daemon - .update_wallet_metadata(&wallet.keys_aliases, &wallet.hardware_wallets) + .update_wallet_metadata(None, &wallet.keys_aliases, &wallet.hardware_wallets) .await?; return Ok(Arc::new(wallet)); } @@ -470,13 +500,33 @@ async fn register_wallet( Ok(wallet) } -pub async fn update_keys_aliases( +pub async fn update_aliases( data_dir: LianaDirectory, network: Network, wallet: Arc, + wallet_alias: Option, keys_aliases: Vec<(Fingerprint, String)>, daemon: Arc, ) -> Result, Error> { + let mut wallet = wallet.as_ref().clone().with_alias(wallet_alias.clone()); + + if let Some(wallet_alias) = wallet_alias.as_ref() { + let network_dir = data_dir.network_directory(network); + let wallet_id = wallet.id(); + update_settings_file(&network_dir, |mut settings| { + if let Some(wallet_setting) = settings + .wallets + .iter_mut() + .find(|w| w.wallet_id() == wallet_id) + { + wallet_setting.alias = Some(wallet_alias.clone()); + } + + settings + }) + .await?; + } + if daemon.backend() != DaemonBackend::RemoteBackend { let network_dir = data_dir.network_directory(network); let wallet_id = wallet.id(); @@ -501,11 +551,18 @@ pub async fn update_keys_aliases( .await?; } - let mut wallet = wallet.as_ref().clone(); wallet.keys_aliases = keys_aliases.into_iter().collect(); daemon - .update_wallet_metadata(&wallet.keys_aliases, &wallet.hardware_wallets) + .update_wallet_metadata( + if wallet_alias.is_some() && wallet.alias != wallet_alias { + wallet_alias + } else { + None + }, + &wallet.keys_aliases, + &wallet.hardware_wallets, + ) .await?; Ok(Arc::new(wallet)) diff --git a/liana-gui/src/app/view/message.rs b/liana-gui/src/app/view/message.rs index 4d85450f..c2641c24 100644 --- a/liana-gui/src/app/view/message.rs +++ b/liana-gui/src/app/view/message.rs @@ -99,6 +99,7 @@ pub enum SettingsMessage { AboutSection, RegisterWallet, FingerprintAliasEdited(Fingerprint, String), + WalletAliasEdited(String), Save, } diff --git a/liana-gui/src/app/view/settings.rs b/liana-gui/src/app/view/settings.rs index f9ce8e21..911a3f78 100644 --- a/liana-gui/src/app/view/settings.rs +++ b/liana-gui/src/app/view/settings.rs @@ -946,10 +946,12 @@ fn is_ok_and(res: &Result, f: impl FnOnce(&T) -> bool) -> bool { } } +#[allow(clippy::too_many_arguments)] pub fn wallet_settings<'a>( cache: &'a Cache, warning: Option<&Error>, descriptor: &'a LianaDescriptor, + wallet_alias: &'a form::Value, keys_aliases: &'a [(Fingerprint, form::Value)], provider_keys: &'a HashMap, processing: bool, @@ -1001,6 +1003,15 @@ pub fn wallet_settings<'a>( let aliases = card::simple( Column::new() + .push(text("Wallet alias:").bold()) + .push( + form::Form::new("Alias", wallet_alias, move |msg| { + Message::Settings(SettingsMessage::WalletAliasEdited(msg)) + }) + .warning("Please enter alias that is not too long") + .size(P1_SIZE) + .padding(10), + ) .push(text("Fingerprint aliases:").bold()) .push(keys_aliases.iter().fold( Column::new().spacing(10), @@ -1038,7 +1049,7 @@ pub fn wallet_settings<'a>( } else { None }) - .push(if !processing { + .push(if !processing && wallet_alias.valid { button::secondary(None, "Update") .on_press(Message::Settings(SettingsMessage::Save)) } else { diff --git a/liana-gui/src/app/wallet.rs b/liana-gui/src/app/wallet.rs index 03f0c054..35fb1e0e 100644 --- a/liana-gui/src/app/wallet.rs +++ b/liana-gui/src/app/wallet.rs @@ -32,6 +32,7 @@ pub fn wallet_name(main_descriptor: &LianaDescriptor) -> String { #[derive(Debug, Clone)] pub struct Wallet { pub name: String, + pub alias: Option, pub main_descriptor: LianaDescriptor, pub descriptor_checksum: String, pub pinned_at: Option, @@ -46,6 +47,7 @@ impl Wallet { pub fn new(main_descriptor: LianaDescriptor) -> Self { Self { name: wallet_name(&main_descriptor), + alias: None, descriptor_checksum: main_descriptor .to_string() .split_once('#') @@ -66,6 +68,11 @@ impl Wallet { self } + pub fn with_alias(mut self, alias: Option) -> Self { + self.alias = alias; + self + } + // To match with WalletSettings.wallet_id pub fn id(&self) -> WalletId { WalletId::new(self.descriptor_checksum.clone(), self.pinned_at) @@ -120,6 +127,7 @@ impl Wallet { Ok(self .with_key_aliases(wallet_settings.keys_aliases()) .with_provider_keys(wallet_settings.provider_keys()) + .with_alias(wallet_settings.alias) .with_name(wallet_settings.name) .with_pinned_at(wallet_settings.pinned_at) .with_hardware_wallets(wallet_settings.hardware_wallets)) diff --git a/liana-gui/src/daemon/mod.rs b/liana-gui/src/daemon/mod.rs index 98a3d410..fa74941d 100644 --- a/liana-gui/src/daemon/mod.rs +++ b/liana-gui/src/daemon/mod.rs @@ -397,6 +397,7 @@ pub trait Daemon: Debug { /// Reimplemented by LianaLite backend async fn update_wallet_metadata( &self, + _wallet_alias: Option, _fingerprint_aliases: &HashMap, _hws: &[HardwareWalletConfig], ) -> Result<(), DaemonError> { diff --git a/liana-gui/src/installer/mod.rs b/liana-gui/src/installer/mod.rs index b7fe6d40..f7f070b5 100644 --- a/liana-gui/src/installer/mod.rs +++ b/liana-gui/src/installer/mod.rs @@ -412,6 +412,7 @@ pub async fn install_local_wallet( let wallet_settings = WalletSettings { name: wallet_name(descriptor), + alias: None, pinned_at: wallet_id.timestamp, descriptor_checksum: wallet_id.descriptor_checksum.clone(), keys: ctx.keys.values().cloned().collect(), @@ -616,7 +617,7 @@ pub async fn create_remote_wallet( }) .collect(); remote_backend - .update_wallet_metadata(&wallet.id, &aliases, &hws) + .update_wallet_metadata(&wallet.id, None, &aliases, &hws) .await .map_err(|e| Error::Unexpected(e.to_string()))?; @@ -627,6 +628,7 @@ pub async fn create_remote_wallet( // keys will be store on the remote backend side and not in the settings file. let wallet_settings = WalletSettings { name: wallet_name(descriptor), + alias: None, descriptor_checksum: wallet_id.descriptor_checksum, pinned_at: wallet_id.timestamp, keys: Vec::new(), @@ -692,6 +694,8 @@ pub async fn import_remote_wallet( .init() .map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?; + let wallet = backend.get_wallet().await?; + // create liana GUI settings file // if the wallet is using the remote backend, then the hardware wallet settings and // keys will be store on the remote backend side and not in the settings file. @@ -701,6 +705,7 @@ pub async fn import_remote_wallet( .as_ref() .expect("Context must have a descriptor at this point"), ), + alias: wallet.metadata.wallet_alias, descriptor_checksum: wallet_id.descriptor_checksum, pinned_at: wallet_id.timestamp, keys: Vec::new(), diff --git a/liana-gui/src/installer/step/backend.rs b/liana-gui/src/installer/step/backend.rs index 1b79c24d..f30aeaa3 100644 --- a/liana-gui/src/installer/step/backend.rs +++ b/liana-gui/src/installer/step/backend.rs @@ -564,7 +564,10 @@ impl Step for ImportRemoteWallet { .map(|invit| invit.wallet_name.as_str()), &self.imported_descriptor, self.error.as_ref(), - self.wallets.iter().map(|w| &w.name).collect(), + self.wallets + .iter() + .map(|w| (&w.name, w.metadata.wallet_alias.as_ref())) + .collect(), ) } } diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs index 19fdfb4b..4e61bd49 100644 --- a/liana-gui/src/installer/view/mod.rs +++ b/liana-gui/src/installer/view/mod.rs @@ -22,7 +22,7 @@ use liana::{ use liana_ui::{ component::{ button, card, collapse, form, hw, separation, - text::{h2, h3, h4_bold, h5_regular, p1_regular, text, Text}, + text::{h2, h3, h4_bold, p1_bold, p1_regular, text, Text}, }, icon, theme, widget::*, @@ -52,18 +52,23 @@ pub fn import_wallet_or_descriptor<'a>( invitation_wallet: Option<&'a str>, imported_descriptor: &'a form::Value, error: Option<&'a String>, - wallets: Vec<&'a String>, + wallets: Vec<(&'a String, Option<&'a String>)>, ) -> Element<'a, Message> { let mut col_wallets = Column::new() .spacing(20) .push(h4_bold("Load a previously used wallet")); let no_wallets = wallets.is_empty(); - for (i, wallet) in wallets.into_iter().enumerate() { + for (i, (name, alias)) in wallets.into_iter().enumerate() { col_wallets = col_wallets.push( - Button::new(h5_regular(wallet).width(Length::Fill)) - .style(theme::button::secondary) - .padding(10) - .on_press(Message::Select(i)), + Button::new( + Column::new() + .push_maybe(alias.map(p1_bold)) + .push(p1_regular(name)) + .width(Length::Fill), + ) + .style(theme::button::secondary) + .padding(10) + .on_press(Message::Select(i)), ); } let card_wallets: Element<'a, Message> = if no_wallets { diff --git a/liana-gui/src/launcher.rs b/liana-gui/src/launcher.rs index 295beec8..c4661e56 100644 --- a/liana-gui/src/launcher.rs +++ b/liana-gui/src/launcher.rs @@ -348,16 +348,20 @@ fn wallets_list_item( Container::new( Button::new( Column::new() - .push(p1_bold(format!( - "My Liana {} wallet", - match network { - Network::Bitcoin => "Bitcoin", - Network::Signet => "Signet", - Network::Testnet => "Testnet", - Network::Regtest => "Regtest", - _ => "", - } - ))) + .push(if let Some(alias) = &settings.alias { + p1_bold(alias) + } else { + p1_bold(format!( + "My Liana {} wallet", + match network { + Network::Bitcoin => "Bitcoin", + Network::Signet => "Signet", + Network::Testnet => "Testnet", + Network::Regtest => "Regtest", + _ => "", + } + )) + }) .push( p1_regular(format!("Liana-{}", settings.descriptor_checksum)) .style(theme::text::secondary), diff --git a/liana-gui/src/main.rs b/liana-gui/src/main.rs index fd942403..d9435a99 100644 --- a/liana-gui/src/main.rs +++ b/liana-gui/src/main.rs @@ -22,7 +22,7 @@ use liana_ui::{component::text, font, image, theme, widget::Element}; use lianad::commands::ListCoinsResult; use liana_gui::{ - app::{self, cache::Cache, wallet::Wallet, App}, + app::{self, cache::Cache, settings::WalletSettings, wallet::Wallet, App}, dir::LianaDirectory, export::import_backup_at_launch, hw::HardwareWalletConfig, @@ -207,9 +207,9 @@ impl GUI { self.log_level .unwrap_or_else(|| cfg.log_level().unwrap_or(LevelFilter::INFO)), ); - if let Some(setting) = settings.remote_backend_auth { + if settings.remote_backend_auth.is_some() { let (login, command) = - login::LianaLiteLogin::new(datadir_path, network, setting); + login::LianaLiteLogin::new(datadir_path, network, settings); self.state = State::Login(Box::new(login)); command.map(|msg| Message::Login(Box::new(msg))) } else { @@ -252,6 +252,7 @@ impl GUI { ); let (app, command) = create_app_with_remote_backend( + l.settings.clone(), backend_client, wallet, coins, @@ -267,9 +268,9 @@ impl GUI { }, (State::Installer(i), Message::Install(msg)) => { if let installer::Message::Exit(settings, internal_bitcoind, remove_log) = *msg { - if let Some(auth) = settings.remote_backend_auth { + if settings.remote_backend_auth.is_some() { let (login, command) = - login::LianaLiteLogin::new(i.datadir.clone(), i.network, auth); + login::LianaLiteLogin::new(i.datadir.clone(), i.network, *settings); self.state = State::Login(Box::new(login)); command.map(|msg| Message::Login(Box::new(msg))) } else { @@ -422,6 +423,7 @@ impl GUI { } pub fn create_app_with_remote_backend( + wallet_settings: WalletSettings, remote_backend: BackendWalletClient, wallet: api::Wallet, coins: ListCoinsResult, @@ -472,6 +474,8 @@ pub fn create_app_with_remote_backend( Arc::new( Wallet::new(wallet.descriptor) .with_name(wallet.name) + .with_alias(wallet_settings.alias) + .with_pinned_at(wallet_settings.pinned_at) .with_key_aliases(aliases) .with_provider_keys(provider_keys) .with_hardware_wallets(hws) diff --git a/liana-gui/src/services/connect/client/backend/api.rs b/liana-gui/src/services/connect/client/backend/api.rs index e4dee4e3..d66cc775 100644 --- a/liana-gui/src/services/connect/client/backend/api.rs +++ b/liana-gui/src/services/connect/client/backend/api.rs @@ -139,6 +139,7 @@ pub struct ProviderKey { #[derive(Debug, Clone, Deserialize)] pub struct WalletMetadata { + pub wallet_alias: Option, pub ledger_hmacs: Vec, pub fingerprint_aliases: Vec, pub provider_keys: Vec, @@ -488,6 +489,7 @@ pub mod payload { #[derive(Serialize)] pub struct UpdateWallet { + pub alias: Option, pub ledger_hmac: Option, pub fingerprint_aliases: Option>, } diff --git a/liana-gui/src/services/connect/client/backend/mod.rs b/liana-gui/src/services/connect/client/backend/mod.rs index a6426205..63a98632 100644 --- a/liana-gui/src/services/connect/client/backend/mod.rs +++ b/liana-gui/src/services/connect/client/backend/mod.rs @@ -183,6 +183,7 @@ impl BackendClient { pub async fn update_wallet_metadata( &self, wallet_uuid: &str, + wallet_alias: Option, fingerprint_aliases: &HashMap, hws: &[HardwareWalletConfig], ) -> Result<(), DaemonError> { @@ -211,6 +212,7 @@ impl BackendClient { ) .await .json(&api::payload::UpdateWallet { + alias: None, ledger_hmac: Some(api::payload::UpdateLedgerHmac { fingerprint: cfg.fingerprint.to_string(), hmac: cfg.token.clone(), @@ -229,16 +231,31 @@ impl BackendClient { } } - if fingerprint_aliases.iter().any(|(fg, alias)| { - !wallet - .metadata - .fingerprint_aliases - .contains(&api::FingerprintAlias { - alias: alias.to_string(), - user_id: self.user_id.clone(), - fingerprint: *fg, - }) - }) { + let fingerprint_aliases: Option> = + if fingerprint_aliases.iter().any(|(fg, alias)| { + !wallet + .metadata + .fingerprint_aliases + .contains(&api::FingerprintAlias { + alias: alias.to_string(), + user_id: self.user_id.clone(), + fingerprint: *fg, + }) + }) { + Some( + fingerprint_aliases + .iter() + .map(|(fg, alias)| api::payload::UpdateFingerprintAlias { + fingerprint: fg.to_string(), + alias: alias.to_string(), + }) + .collect(), + ) + } else { + None + }; + + if fingerprint_aliases.is_some() || wallet_alias.is_some() { let response: Response = self .request( Method::PATCH, @@ -246,16 +263,9 @@ impl BackendClient { ) .await .json(&api::payload::UpdateWallet { + alias: wallet_alias, ledger_hmac: None, - fingerprint_aliases: Some( - fingerprint_aliases - .iter() - .map(|(fg, alias)| api::payload::UpdateFingerprintAlias { - fingerprint: fg.to_string(), - alias: alias.to_string(), - }) - .collect(), - ), + fingerprint_aliases, }) .send() .await?; @@ -342,7 +352,7 @@ impl BackendWalletClient { self.inner.user_email() } - async fn get_wallet(&self) -> Result { + pub async fn get_wallet(&self) -> Result { let list = self.inner.list_wallets().await?; let wallet = list .into_iter() @@ -1133,11 +1143,12 @@ impl Daemon for BackendWalletClient { /// Implemented by LianaLite backend async fn update_wallet_metadata( &self, + wallet_alias: Option, fingerprint_aliases: &HashMap, hws: &[HardwareWalletConfig], ) -> Result<(), DaemonError> { self.inner - .update_wallet_metadata(&self.wallet_uuid, fingerprint_aliases, hws) + .update_wallet_metadata(&self.wallet_uuid, wallet_alias, fingerprint_aliases, hws) .await } diff --git a/liana-gui/src/services/connect/login.rs b/liana-gui/src/services/connect/login.rs index e063cb8f..e4c95292 100644 --- a/liana-gui/src/services/connect/login.rs +++ b/liana-gui/src/services/connect/login.rs @@ -13,7 +13,7 @@ use lianad::commands::ListCoinsResult; use crate::{ app::{ cache::coins_to_cache, - settings::{AuthConfig, SettingsError}, + settings::{SettingsError, WalletSettings}, }, daemon::DaemonError, dir::LianaDirectory, @@ -112,6 +112,7 @@ pub enum BackendState { pub struct LianaLiteLogin { pub datadir: LianaDirectory, pub network: Network, + pub settings: WalletSettings, wallet_id: String, email: String, @@ -139,16 +140,18 @@ impl LianaLiteLogin { pub fn new( datadir: LianaDirectory, network: Network, - setting: AuthConfig, + settings: WalletSettings, ) -> (Self, Task) { + let auth = settings.remote_backend_auth.clone().unwrap(); ( Self { network, datadir: datadir.clone(), step: ConnectionStep::CheckingAuthFile, connection_error: None, - wallet_id: setting.wallet_id.clone(), - email: setting.email.clone(), + settings, + wallet_id: auth.wallet_id.clone(), + email: auth.email.clone(), auth_error: None, processing: true, }, @@ -160,11 +163,11 @@ impl LianaLiteLogin { let client = AuthClient::new( service_config.auth_api_url, service_config.auth_api_public_key, - setting.email, + auth.email, ); connect_with_credentials( client, - setting.wallet_id, + auth.wallet_id, service_config.backend_api_url, network, datadir, From 36fb3d877552099ef7fbdce21753352a1c4219df Mon Sep 17 00:00:00 2001 From: edouardparis Date: Mon, 19 May 2025 18:46:41 +0200 Subject: [PATCH 2/4] Add wallet alias in window title --- liana-gui/src/app/mod.rs | 9 +++++++++ liana-gui/src/main.rs | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/liana-gui/src/app/mod.rs b/liana-gui/src/app/mod.rs index 7a1d2f12..3793ca02 100644 --- a/liana-gui/src/app/mod.rs +++ b/liana-gui/src/app/mod.rs @@ -182,6 +182,15 @@ impl App { ) } + pub fn title(&self) -> String { + if let Some(alias) = &self.wallet.alias { + if !alias.is_empty() { + return format!("- {}", alias); + } + } + String::new() + } + fn set_current_panel(&mut self, menu: Menu) -> Task { self.panels.current_mut().interrupt(); diff --git a/liana-gui/src/main.rs b/liana-gui/src/main.rs index d9435a99..cfb392ed 100644 --- a/liana-gui/src/main.rs +++ b/liana-gui/src/main.rs @@ -135,8 +135,9 @@ async fn ctrl_c() -> Result<(), ()> { impl GUI { fn title(&self) -> String { - match self.state { + match &self.state { State::Installer(_) => format!("Liana v{} Installer", VERSION), + State::App(a) => format!("Liana v{} {}", VERSION, a.title()), _ => format!("Liana v{}", VERSION), } } From 51d913b9efff2ec360190fc75e0013d691dd8d7e Mon Sep 17 00:00:00 2001 From: edouardparis Date: Tue, 20 May 2025 11:09:00 +0200 Subject: [PATCH 3/4] Set wallet alias in settings if liana-connect wallet alias changed --- liana-gui/src/main.rs | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/liana-gui/src/main.rs b/liana-gui/src/main.rs index cfb392ed..c33da20a 100644 --- a/liana-gui/src/main.rs +++ b/liana-gui/src/main.rs @@ -22,7 +22,13 @@ use liana_ui::{component::text, font, image, theme, widget::Element}; use lianad::commands::ListCoinsResult; use liana_gui::{ - app::{self, cache::Cache, settings::WalletSettings, wallet::Wallet, App}, + app::{ + self, + cache::Cache, + settings::{update_settings_file, WalletSettings}, + wallet::Wallet, + App, + }, dir::LianaDirectory, export::import_backup_at_launch, hw::HardwareWalletConfig, @@ -428,10 +434,32 @@ pub fn create_app_with_remote_backend( remote_backend: BackendWalletClient, wallet: api::Wallet, coins: ListCoinsResult, - datadir: LianaDirectory, + liana_dir: LianaDirectory, network: bitcoin::Network, config: app::Config, ) -> (app::App, iced::Task) { + // If someone modified the wallet_alias on Liana-Connect, + // then the new alias is imported and stored in the settings file. + if wallet.metadata.wallet_alias != wallet_settings.alias { + let network_directory = liana_dir.network_directory(network); + if let Err(e) = tokio::runtime::Handle::current().block_on(async { + update_settings_file(&network_directory, |mut settings| { + if let Some(w) = settings + .wallets + .iter_mut() + .find(|w| w.wallet_id() == wallet_settings.wallet_id()) + { + w.alias = wallet.metadata.wallet_alias.clone(); + tracing::info!("Wallet alias was changed. Settings updated."); + } + settings + }) + .await + }) { + tracing::error!("Failed to update wallet settings with remote alias: {}", e); + } + } + let hws: Vec = wallet .metadata .ledger_hmacs @@ -460,13 +488,14 @@ pub fn create_app_with_remote_backend( .into_iter() .map(|pk| (pk.fingerprint, pk.into())) .collect(); + App::new( Cache { network, coins: coins.coins, rescan_progress: None, sync_progress: 1.0, // Remote backend is always synced - datadir_path: datadir.clone(), + datadir_path: liana_dir.clone(), blockheight: wallet.tip_height.unwrap_or(0), // We ignore last poll fields for remote backend. last_poll_timestamp: None, @@ -475,17 +504,17 @@ pub fn create_app_with_remote_backend( Arc::new( Wallet::new(wallet.descriptor) .with_name(wallet.name) - .with_alias(wallet_settings.alias) + .with_alias(wallet.metadata.wallet_alias) .with_pinned_at(wallet_settings.pinned_at) .with_key_aliases(aliases) .with_provider_keys(provider_keys) .with_hardware_wallets(hws) - .load_hotsigners(&datadir, network) + .load_hotsigners(&liana_dir, network) .expect("Datadir should be conform"), ), config, Arc::new(remote_backend), - datadir, + liana_dir, None, false, ) From a9917d17810b3d47c0bd5623ffa7636e2f299c72 Mon Sep 17 00:00:00 2001 From: edouardparis Date: Tue, 20 May 2025 12:12:59 +0200 Subject: [PATCH 4/4] Add wallet alias step to installer --- liana-gui/src/app/state/settings/wallet.rs | 3 +- liana-gui/src/installer/context.rs | 2 + liana-gui/src/installer/message.rs | 1 + liana-gui/src/installer/mod.rs | 20 ++++--- liana-gui/src/installer/step/backend.rs | 9 +++ liana-gui/src/installer/step/mod.rs | 2 + liana-gui/src/installer/step/wallet_alias.rs | 55 +++++++++++++++++++ liana-gui/src/installer/view/mod.rs | 41 +++++++++++++- .../services/connect/client/backend/api.rs | 2 + 9 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 liana-gui/src/installer/step/wallet_alias.rs diff --git a/liana-gui/src/app/state/settings/wallet.rs b/liana-gui/src/app/state/settings/wallet.rs index 8d15f42e..b69191e8 100644 --- a/liana-gui/src/app/state/settings/wallet.rs +++ b/liana-gui/src/app/state/settings/wallet.rs @@ -29,6 +29,7 @@ use crate::{ dir::LianaDirectory, export::{ImportExportMessage, ImportExportType}, hw::{HardwareWallet, HardwareWalletConfig, HardwareWallets}, + services::connect::client::backend::api::WALLET_ALIAS_MAXIMUM_LENGTH, }; enum Modal { @@ -168,7 +169,7 @@ impl State for WalletSettingsState { Message::View(view::Message::Settings(view::SettingsMessage::WalletAliasEdited( alias, ))) => { - self.wallet_alias.valid = alias.len() < 64; + self.wallet_alias.valid = alias.len() < WALLET_ALIAS_MAXIMUM_LENGTH; self.wallet_alias.value = alias; Task::none() } diff --git a/liana-gui/src/installer/context.rs b/liana-gui/src/installer/context.rs index 811985a9..1dcd03a4 100644 --- a/liana-gui/src/installer/context.rs +++ b/liana-gui/src/installer/context.rs @@ -71,6 +71,7 @@ pub struct Context { pub internal_bitcoind: Option, pub remote_backend: RemoteBackend, pub backup: Option, + pub wallet_alias: String, } impl Context { @@ -97,6 +98,7 @@ impl Context { internal_bitcoind_config: None, internal_bitcoind: None, remote_backend, + wallet_alias: String::new(), backup: None, } } diff --git a/liana-gui/src/installer/message.rs b/liana-gui/src/installer/message.rs index 2eac96c6..f1a4cd7d 100644 --- a/liana-gui/src/installer/message.rs +++ b/liana-gui/src/installer/message.rs @@ -66,6 +66,7 @@ pub enum Message { ImportExport(ImportExportMessage), ImportBackup, WalletFromBackup((HashMap, Backup)), + WalletAliasEdited(String), } impl Close for Message { diff --git a/liana-gui/src/installer/mod.rs b/liana-gui/src/installer/mod.rs index f7f070b5..354f73c7 100644 --- a/liana-gui/src/installer/mod.rs +++ b/liana-gui/src/installer/mod.rs @@ -13,7 +13,7 @@ use liana_ui::{ widget::{Column, Element}, }; use lianad::config::{BitcoinBackend, BitcoindConfig, BitcoindRpcAuth, Config}; -use std::ops::Deref; +use std::{collections::HashMap, ops::Deref}; use tokio::runtime::Handle; use tracing::{error, info, warn}; @@ -28,7 +28,7 @@ use crate::{ wallet::wallet_name, }, backup, - daemon::DaemonError, + daemon::{Daemon, DaemonError}, delete, dir::LianaDirectory, hw::{HardwareWalletConfig, HardwareWallets}, @@ -52,7 +52,7 @@ use step::{ BackupDescriptor, BackupMnemonic, ChooseBackend, ChooseDescriptorTemplate, DefineDescriptor, DefineNode, DescriptorTemplateDescription, Final, ImportDescriptor, ImportRemoteWallet, InternalBitcoindStep, RecoverMnemonic, RegisterDescriptor, RemoteBackendLogin, - SelectBitcoindTypeStep, ShareXpubs, Step, + SelectBitcoindTypeStep, ShareXpubs, Step, WalletAlias, }; #[derive(Debug, Clone)] @@ -141,6 +141,7 @@ impl Installer { SelectBitcoindTypeStep::new().into(), InternalBitcoindStep::new(&context.liana_directory).into(), DefineNode::default().into(), + WalletAlias::default().into(), Final::new().into(), ], UserFlow::ShareXpubs => vec![ShareXpubs::new(network, signer.clone()).into()], @@ -154,6 +155,7 @@ impl Installer { SelectBitcoindTypeStep::new().into(), InternalBitcoindStep::new(&context.liana_directory).into(), DefineNode::default().into(), + WalletAlias::default().into(), Final::new().into(), ], }, @@ -412,7 +414,7 @@ pub async fn install_local_wallet( let wallet_settings = WalletSettings { name: wallet_name(descriptor), - alias: None, + alias: Some(ctx.wallet_alias.clone()), pinned_at: wallet_id.timestamp, descriptor_checksum: wallet_id.descriptor_checksum.clone(), keys: ctx.keys.values().cloned().collect(), @@ -617,7 +619,7 @@ pub async fn create_remote_wallet( }) .collect(); remote_backend - .update_wallet_metadata(&wallet.id, None, &aliases, &hws) + .update_wallet_metadata(&wallet.id, Some(ctx.wallet_alias.clone()), &aliases, &hws) .await .map_err(|e| Error::Unexpected(e.to_string()))?; @@ -628,7 +630,7 @@ pub async fn create_remote_wallet( // keys will be store on the remote backend side and not in the settings file. let wallet_settings = WalletSettings { name: wallet_name(descriptor), - alias: None, + alias: Some(ctx.wallet_alias.clone()), descriptor_checksum: wallet_id.descriptor_checksum, pinned_at: wallet_id.timestamp, keys: Vec::new(), @@ -694,7 +696,9 @@ pub async fn import_remote_wallet( .init() .map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?; - let wallet = backend.get_wallet().await?; + backend + .update_wallet_metadata(Some(ctx.wallet_alias.clone()), &HashMap::new(), &[]) + .await?; // create liana GUI settings file // if the wallet is using the remote backend, then the hardware wallet settings and @@ -705,7 +709,7 @@ pub async fn import_remote_wallet( .as_ref() .expect("Context must have a descriptor at this point"), ), - alias: wallet.metadata.wallet_alias, + alias: Some(ctx.wallet_alias.clone()), descriptor_checksum: wallet_id.descriptor_checksum, pinned_at: wallet_id.timestamp, keys: Vec::new(), diff --git a/liana-gui/src/installer/step/backend.rs b/liana-gui/src/installer/step/backend.rs index f30aeaa3..8ac2361d 100644 --- a/liana-gui/src/installer/step/backend.rs +++ b/liana-gui/src/installer/step/backend.rs @@ -350,6 +350,9 @@ pub struct ImportRemoteWallet { error: Option, backend: context::RemoteBackend, wallets: Vec, + // wallet alias is stored here to be applied to context + // and be modified in a following step + wallet_alias: Option, } impl ImportRemoteWallet { @@ -363,6 +366,7 @@ impl ImportRemoteWallet { error: None, backend: context::RemoteBackend::Undefined, wallets: Vec::new(), + wallet_alias: None, } } } @@ -514,6 +518,7 @@ impl Step for ImportRemoteWallet { } Message::Select(i) => { if let Some(wallet) = self.wallets.get(i).cloned() { + self.wallet_alias = wallet.metadata.wallet_alias.clone(); self.backend = match self.backend.clone() { context::RemoteBackend::WithoutWallet(backend) => { context::RemoteBackend::WithWallet( @@ -546,6 +551,10 @@ impl Step for ImportRemoteWallet { ctx.descriptor.clone_from(&self.descriptor); ctx.remote_backend.clone_from(&self.backend); + if let Some(alias) = &self.wallet_alias { + ctx.wallet_alias = alias.clone(); + } + true } diff --git a/liana-gui/src/installer/step/mod.rs b/liana-gui/src/installer/step/mod.rs index fbca9973..cf3d31bf 100644 --- a/liana-gui/src/installer/step/mod.rs +++ b/liana-gui/src/installer/step/mod.rs @@ -4,6 +4,7 @@ mod backend; mod mnemonic; mod node; mod share_xpubs; +mod wallet_alias; pub use node::{ bitcoind::{DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep}, @@ -20,6 +21,7 @@ pub use backend::{ChooseBackend, ImportRemoteWallet, RemoteBackendLogin}; pub use mnemonic::{BackupMnemonic, RecoverMnemonic}; pub use share_xpubs::ShareXpubs; use tracing::warn; +pub use wallet_alias::WalletAlias; use std::collections::HashMap; diff --git a/liana-gui/src/installer/step/wallet_alias.rs b/liana-gui/src/installer/step/wallet_alias.rs new file mode 100644 index 00000000..f79f2494 --- /dev/null +++ b/liana-gui/src/installer/step/wallet_alias.rs @@ -0,0 +1,55 @@ +use iced::Task; + +use liana_ui::{component::form, widget::*}; + +use crate::{ + hw::HardwareWallets, + installer::{context::Context, message::Message, step::Step, view}, + services::connect::client::backend::api::WALLET_ALIAS_MAXIMUM_LENGTH, +}; + +#[derive(Default)] +pub struct WalletAlias { + wallet_alias: form::Value, +} + +impl Step for WalletAlias { + fn load_context(&mut self, ctx: &Context) { + if !ctx.wallet_alias.is_empty() { + self.wallet_alias.value = ctx.wallet_alias.clone(); + self.wallet_alias.valid = true; + } + } + + fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Task { + if let Message::WalletAliasEdited(alias) = message { + self.wallet_alias.valid = alias.len() < WALLET_ALIAS_MAXIMUM_LENGTH; + self.wallet_alias.value = alias; + } + Task::none() + } + + fn view<'a>( + &'a self, + _hws: &'a HardwareWallets, + progress: (usize, usize), + email: Option<&'a str>, + ) -> Element { + view::wallet_alias(progress, email, &self.wallet_alias) + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + if self.wallet_alias.valid { + ctx.wallet_alias = self.wallet_alias.value.trim().to_string(); + true + } else { + false + } + } +} + +impl From for Box { + fn from(s: WalletAlias) -> Box { + Box::new(s) + } +} diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs index 4e61bd49..34e71366 100644 --- a/liana-gui/src/installer/view/mod.rs +++ b/liana-gui/src/installer/view/mod.rs @@ -9,7 +9,7 @@ use iced::{ }; use async_hwi::DeviceKind; -use liana_ui::component::text; +use liana_ui::component::text::{self, p2_regular}; use std::collections::HashMap; use std::net::{Ipv4Addr, Ipv6Addr}; use std::path::PathBuf; @@ -2146,6 +2146,45 @@ pub const REMOTE_BACKEND_DESC: &str = "Use our service to instantly be ready to pub const LOCAL_WALLET_DESC: &str = "Use your already existing Bitcoin node or automatically install one. The Liana wallet will not connect to any external server.\n\nThis is the most private option, but the data is locally stored on this computer, only. You must perform your own backups, and share the descriptor with other people you want to be able to access the wallet"; +pub fn wallet_alias<'a>( + progress: (usize, usize), + email: Option<&'a str>, + wallet_alias: &form::Value, +) -> Element<'a, Message> { + layout( + progress, + email, + "Give your wallet an alias", + Column::new() + .push( + Column::new() + .spacing(20) + .push(p1_bold("Wallet alias:")) + .push( + form::Form::new("Wallet alias", wallet_alias, Message::WalletAliasEdited) + .warning("Wallet alias is too long.") + .size(text::P1_SIZE) + .padding(10), + ) + .push(p2_regular( + "You will be able to change it later in Settings > Wallet", + )), + ) + .push( + button::secondary(None, "Next") + .width(Length::Fixed(200.0)) + .on_press_maybe(if wallet_alias.valid { + Some(Message::Next) + } else { + None + }), + ) + .spacing(50), + true, + Some(Message::Previous), + ) +} + fn layout<'a>( progress: (usize, usize), email: Option<&'a str>, diff --git a/liana-gui/src/services/connect/client/backend/api.rs b/liana-gui/src/services/connect/client/backend/api.rs index d66cc775..48a37e57 100644 --- a/liana-gui/src/services/connect/client/backend/api.rs +++ b/liana-gui/src/services/connect/client/backend/api.rs @@ -145,6 +145,8 @@ pub struct WalletMetadata { pub provider_keys: Vec, } +pub const WALLET_ALIAS_MAXIMUM_LENGTH: usize = 64; + #[derive(Debug, Clone, Deserialize)] pub struct LedgerHmac { #[serde(deserialize_with = "deser_fromstr")]