Merge #1713: Update wallet alias
a9917d17810b3d47c0bd5623ffa7636e2f299c72 Add wallet alias step to installer (edouardparis)
51d913b9efff2ec360190fc75e0013d691dd8d7e Set wallet alias in settings if liana-connect wallet alias changed (edouardparis)
36fb3d877552099ef7fbdce21753352a1c4219df Add wallet alias in window title (edouardparis)
381421038b0e12ed257679b31a16102021d6d76a Add wallet alias to settings (edouardparis)
Pull request description:
We had a new `wallet_alias` to settings file.
This field is displayed in the launcher when wallets are listed and in the window title when the app is running the wallet.
The field is also store in liana-connect wallet.metadata field to facilitate import of wallet from Liana-Connect.
If the field was modified on Liana-Connect, then next time the user open the wallet in the gui, the settings field is updated.
User can modify the wallet alias in the Settings > Wallet section.
ACKs for top commit:
edouardparis:
Self-ACK a9917d17810b3d47c0bd5623ffa7636e2f299c72
Tree-SHA512: 819fb84660894a8c546132a21966c8084b4820b4cb7f57c9de80648355ebf053581964dea8e8fe455523e0c7692244fc0803e8dec03d50946139ed88832622f7
This commit is contained in:
commit
0ba12eb689
@ -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<Message> {
|
||||
self.panels.current_mut().interrupt();
|
||||
|
||||
|
||||
@ -132,6 +132,7 @@ impl AuthConfig {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct WalletSettings {
|
||||
pub name: String,
|
||||
pub alias: Option<String>,
|
||||
pub descriptor_checksum: String,
|
||||
pub pinned_at: Option<i64>,
|
||||
// if wallet is using remote backend, then this information is stored on the remote backend
|
||||
|
||||
@ -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 {
|
||||
@ -49,6 +50,7 @@ pub struct WalletSettingsState {
|
||||
descriptor: LianaDescriptor,
|
||||
keys_aliases: Vec<(Fingerprint, form::Value<String>)>,
|
||||
wallet: Arc<Wallet>,
|
||||
wallet_alias: form::Value<String>,
|
||||
modal: Modal,
|
||||
processing: bool,
|
||||
updated: bool,
|
||||
@ -61,6 +63,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 +109,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 +166,13 @@ impl State for WalletSettingsState {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Settings(view::SettingsMessage::WalletAliasEdited(
|
||||
alias,
|
||||
))) => {
|
||||
self.wallet_alias.valid = alias.len() < WALLET_ALIAS_MAXIMUM_LENGTH;
|
||||
self.wallet_alias.value = alias;
|
||||
Task::none()
|
||||
}
|
||||
Message::View(view::Message::Settings(
|
||||
view::SettingsMessage::FingerprintAliasEdited(fg, value),
|
||||
)) => {
|
||||
@ -176,10 +190,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 +238,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 +493,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 +501,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>,
|
||||
wallet_alias: Option<String>,
|
||||
keys_aliases: Vec<(Fingerprint, String)>,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
) -> Result<Arc<Wallet>, 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 +552,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))
|
||||
|
||||
@ -99,6 +99,7 @@ pub enum SettingsMessage {
|
||||
AboutSection,
|
||||
RegisterWallet,
|
||||
FingerprintAliasEdited(Fingerprint, String),
|
||||
WalletAliasEdited(String),
|
||||
Save,
|
||||
}
|
||||
|
||||
|
||||
@ -946,10 +946,12 @@ fn is_ok_and<T, E>(res: &Result<T, E>, 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<String>,
|
||||
keys_aliases: &'a [(Fingerprint, form::Value<String>)],
|
||||
provider_keys: &'a HashMap<Fingerprint, ProviderKey>,
|
||||
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 {
|
||||
|
||||
@ -32,6 +32,7 @@ pub fn wallet_name(main_descriptor: &LianaDescriptor) -> String {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Wallet {
|
||||
pub name: String,
|
||||
pub alias: Option<String>,
|
||||
pub main_descriptor: LianaDescriptor,
|
||||
pub descriptor_checksum: String,
|
||||
pub pinned_at: Option<i64>,
|
||||
@ -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<String>) -> 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))
|
||||
|
||||
@ -397,6 +397,7 @@ pub trait Daemon: Debug {
|
||||
/// Reimplemented by LianaLite backend
|
||||
async fn update_wallet_metadata(
|
||||
&self,
|
||||
_wallet_alias: Option<String>,
|
||||
_fingerprint_aliases: &HashMap<Fingerprint, String>,
|
||||
_hws: &[HardwareWalletConfig],
|
||||
) -> Result<(), DaemonError> {
|
||||
|
||||
@ -71,6 +71,7 @@ pub struct Context {
|
||||
pub internal_bitcoind: Option<Bitcoind>,
|
||||
pub remote_backend: RemoteBackend,
|
||||
pub backup: Option<Backup>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +66,7 @@ pub enum Message {
|
||||
ImportExport(ImportExportMessage),
|
||||
ImportBackup,
|
||||
WalletFromBackup((HashMap<Fingerprint, settings::KeySetting>, Backup)),
|
||||
WalletAliasEdited(String),
|
||||
}
|
||||
|
||||
impl Close for Message {
|
||||
|
||||
@ -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,6 +414,7 @@ pub async fn install_local_wallet(
|
||||
|
||||
let wallet_settings = WalletSettings {
|
||||
name: wallet_name(descriptor),
|
||||
alias: Some(ctx.wallet_alias.clone()),
|
||||
pinned_at: wallet_id.timestamp,
|
||||
descriptor_checksum: wallet_id.descriptor_checksum.clone(),
|
||||
keys: ctx.keys.values().cloned().collect(),
|
||||
@ -616,7 +619,7 @@ pub async fn create_remote_wallet(
|
||||
})
|
||||
.collect();
|
||||
remote_backend
|
||||
.update_wallet_metadata(&wallet.id, &aliases, &hws)
|
||||
.update_wallet_metadata(&wallet.id, Some(ctx.wallet_alias.clone()), &aliases, &hws)
|
||||
.await
|
||||
.map_err(|e| Error::Unexpected(e.to_string()))?;
|
||||
|
||||
@ -627,6 +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: Some(ctx.wallet_alias.clone()),
|
||||
descriptor_checksum: wallet_id.descriptor_checksum,
|
||||
pinned_at: wallet_id.timestamp,
|
||||
keys: Vec::new(),
|
||||
@ -692,6 +696,10 @@ pub async fn import_remote_wallet(
|
||||
.init()
|
||||
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
|
||||
|
||||
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
|
||||
// keys will be store on the remote backend side and not in the settings file.
|
||||
@ -701,6 +709,7 @@ pub async fn import_remote_wallet(
|
||||
.as_ref()
|
||||
.expect("Context must have a descriptor at this point"),
|
||||
),
|
||||
alias: Some(ctx.wallet_alias.clone()),
|
||||
descriptor_checksum: wallet_id.descriptor_checksum,
|
||||
pinned_at: wallet_id.timestamp,
|
||||
keys: Vec::new(),
|
||||
|
||||
@ -350,6 +350,9 @@ pub struct ImportRemoteWallet {
|
||||
error: Option<String>,
|
||||
backend: context::RemoteBackend,
|
||||
wallets: Vec<api::Wallet>,
|
||||
// wallet alias is stored here to be applied to context
|
||||
// and be modified in a following step
|
||||
wallet_alias: Option<String>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -564,7 +573,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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
55
liana-gui/src/installer/step/wallet_alias.rs
Normal file
55
liana-gui/src/installer/step/wallet_alias.rs
Normal file
@ -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<String>,
|
||||
}
|
||||
|
||||
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<Message> {
|
||||
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<Message> {
|
||||
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<WalletAlias> for Box<dyn Step> {
|
||||
fn from(s: WalletAlias) -> Box<dyn Step> {
|
||||
Box::new(s)
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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<String>,
|
||||
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 {
|
||||
@ -2141,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<String>,
|
||||
) -> 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>,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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, wallet::Wallet, App},
|
||||
app::{
|
||||
self,
|
||||
cache::Cache,
|
||||
settings::{update_settings_file, WalletSettings},
|
||||
wallet::Wallet,
|
||||
App,
|
||||
},
|
||||
dir::LianaDirectory,
|
||||
export::import_backup_at_launch,
|
||||
hw::HardwareWalletConfig,
|
||||
@ -135,8 +141,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),
|
||||
}
|
||||
}
|
||||
@ -207,9 +214,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 +259,7 @@ impl GUI {
|
||||
);
|
||||
|
||||
let (app, command) = create_app_with_remote_backend(
|
||||
l.settings.clone(),
|
||||
backend_client,
|
||||
wallet,
|
||||
coins,
|
||||
@ -267,9 +275,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,13 +430,36 @@ impl GUI {
|
||||
}
|
||||
|
||||
pub fn create_app_with_remote_backend(
|
||||
wallet_settings: WalletSettings,
|
||||
remote_backend: BackendWalletClient,
|
||||
wallet: api::Wallet,
|
||||
coins: ListCoinsResult,
|
||||
datadir: LianaDirectory,
|
||||
liana_dir: LianaDirectory,
|
||||
network: bitcoin::Network,
|
||||
config: app::Config,
|
||||
) -> (app::App, iced::Task<app::Message>) {
|
||||
// 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<HardwareWalletConfig> = wallet
|
||||
.metadata
|
||||
.ledger_hmacs
|
||||
@ -457,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,
|
||||
@ -472,15 +504,17 @@ pub fn create_app_with_remote_backend(
|
||||
Arc::new(
|
||||
Wallet::new(wallet.descriptor)
|
||||
.with_name(wallet.name)
|
||||
.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,
|
||||
)
|
||||
|
||||
@ -139,11 +139,14 @@ pub struct ProviderKey {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct WalletMetadata {
|
||||
pub wallet_alias: Option<String>,
|
||||
pub ledger_hmacs: Vec<LedgerHmac>,
|
||||
pub fingerprint_aliases: Vec<FingerprintAlias>,
|
||||
pub provider_keys: Vec<ProviderKey>,
|
||||
}
|
||||
|
||||
pub const WALLET_ALIAS_MAXIMUM_LENGTH: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LedgerHmac {
|
||||
#[serde(deserialize_with = "deser_fromstr")]
|
||||
@ -488,6 +491,7 @@ pub mod payload {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateWallet {
|
||||
pub alias: Option<String>,
|
||||
pub ledger_hmac: Option<UpdateLedgerHmac>,
|
||||
pub fingerprint_aliases: Option<Vec<UpdateFingerprintAlias>>,
|
||||
}
|
||||
|
||||
@ -183,6 +183,7 @@ impl BackendClient {
|
||||
pub async fn update_wallet_metadata(
|
||||
&self,
|
||||
wallet_uuid: &str,
|
||||
wallet_alias: Option<String>,
|
||||
fingerprint_aliases: &HashMap<Fingerprint, String>,
|
||||
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<Vec<api::payload::UpdateFingerprintAlias>> =
|
||||
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<api::Wallet, DaemonError> {
|
||||
pub async fn get_wallet(&self) -> Result<api::Wallet, DaemonError> {
|
||||
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<String>,
|
||||
fingerprint_aliases: &HashMap<Fingerprint, String>,
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -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<Message>) {
|
||||
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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user