diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index a0be0322..5c2434d7 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -12,7 +12,7 @@ use async_hwi::{DeviceKind, Version}; #[derive(Debug, Clone)] pub enum Message { CreateWallet, - ParticipateWallet, + ShareXpubs, ImportWallet, UserActionDone(bool), Exit(PathBuf, Option), diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 29fd1cc4..89bc6109 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -27,8 +27,8 @@ use crate::{ pub use message::Message; use step::{ BackupDescriptor, BackupMnemonic, DefineBitcoind, DefineDescriptor, Final, ImportDescriptor, - InternalBitcoindStep, ParticipateXpub, RecoverMnemonic, RegisterDescriptor, - SelectBitcoindTypeStep, Step, Welcome, + InternalBitcoindStep, RecoverMnemonic, RegisterDescriptor, SelectBitcoindTypeStep, ShareXpubs, + Step, Welcome, }; pub struct Installer { @@ -155,17 +155,10 @@ impl Installer { ]; self.next() } - Message::ParticipateWallet => { + Message::ShareXpubs => { self.steps = vec![ Welcome::default().into(), - ParticipateXpub::new(self.network, self.signer.clone()).into(), - ImportDescriptor::new(self.network).into(), - BackupMnemonic::new(self.signer.clone()).into(), - RegisterDescriptor::new_import_wallet().into(), - SelectBitcoindTypeStep::new().into(), - InternalBitcoindStep::new(&self.context.data_dir).into(), - DefineBitcoind::new().into(), - Final::new().into(), + ShareXpubs::new(self.network, self.signer.clone()).into(), ]; self.next() } diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index caa1613b..e693ce08 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -9,7 +9,7 @@ use liana::{ descriptors::{LianaDescriptor, LianaPolicy, PathInfo}, miniscript::{ bitcoin::{ - bip32::{ChildNumber, DerivationPath, Fingerprint}, + bip32::{DerivationPath, Fingerprint}, Network, }, descriptor::{ @@ -1064,7 +1064,7 @@ impl DescriptorEditModal for EditXpubModal { } } -fn default_derivation_path(network: Network) -> DerivationPath { +pub fn default_derivation_path(network: Network) -> DerivationPath { DerivationPath::from_str({ if network == Network::Bitcoin { "m/48'/0'/0'/2'" @@ -1077,7 +1077,7 @@ fn default_derivation_path(network: Network) -> DerivationPath { /// LIANA_STANDARD_PATH: m/48'/0'/0'/2'; /// LIANA_TESTNET_STANDARD_PATH: m/48'/1'/0'/2'; -async fn get_extended_pubkey( +pub async fn get_extended_pubkey( hw: std::sync::Arc, fingerprint: Fingerprint, network: Network, @@ -1095,178 +1095,6 @@ async fn get_extended_pubkey( })) } -pub struct HardwareWalletXpubs { - fingerprint: Fingerprint, - xpubs: Vec, - processing: bool, - error: Option, -} - -pub struct SignerXpubs { - signer: Arc>, - xpubs: Vec, - next_account: ChildNumber, -} - -impl SignerXpubs { - fn new(signer: Arc>) -> Self { - Self { - signer, - xpubs: Vec::new(), - next_account: ChildNumber::from_hardened_idx(0).unwrap(), - } - } - - fn select(&mut self, network: Network) { - self.next_account = self.next_account.increment().unwrap(); - let signer = self.signer.lock().unwrap(); - let derivation_path = default_derivation_path(network); - // We keep only one for the moment. - self.xpubs = vec![format!( - "[{}{}]{}", - signer.fingerprint(), - derivation_path.to_string().trim_start_matches('m'), - signer.get_extended_pubkey(&derivation_path) - )]; - } - - pub fn view(&self) -> Element { - view::signer_xpubs(&self.xpubs) - } -} - -pub struct ParticipateXpub { - network: Network, - - shared: bool, - - hw_xpubs: Vec, - xpubs_signer: SignerXpubs, -} - -impl ParticipateXpub { - pub fn new(network: Network, signer: Arc>) -> Self { - Self { - network, - hw_xpubs: Vec::new(), - shared: false, - xpubs_signer: SignerXpubs::new(signer), - } - } -} - -impl Step for ParticipateXpub { - // form value is set as valid each time it is edited. - // Verification of the values is happening when the user click on Next button. - fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command { - match message { - Message::UserActionDone(shared) => self.shared = shared, - Message::ImportXpub(fg, res) => { - if let Some(hw_xpubs) = self.hw_xpubs.iter_mut().find(|x| x.fingerprint == fg) { - hw_xpubs.processing = false; - match res { - Err(e) => { - hw_xpubs.error = e.into(); - } - Ok(xpub) => { - hw_xpubs.error = None; - // We keep only one for the moment. - hw_xpubs.xpubs = vec![xpub.to_string()]; - } - } - } - } - Message::UseHotSigner => { - self.xpubs_signer.select(self.network); - } - Message::Select(i) => { - if let Some(HardwareWallet::Supported { - device, - fingerprint, - .. - }) = hws.list.get(i) - { - let device = device.clone(); - let fingerprint = *fingerprint; - let network = self.network; - if let Some(hw_xpubs) = self - .hw_xpubs - .iter_mut() - .find(|x| x.fingerprint == fingerprint) - { - hw_xpubs.processing = true; - hw_xpubs.error = None; - } else { - self.hw_xpubs.push(HardwareWalletXpubs { - fingerprint, - xpubs: Vec::new(), - processing: true, - error: None, - }); - } - return Command::perform( - async move { - ( - fingerprint, - get_extended_pubkey(device, fingerprint, network).await, - ) - }, - |(fingerprint, res)| Message::ImportXpub(fingerprint, res), - ); - } - } - _ => {} - }; - Command::none() - } - - fn subscription(&self, hws: &HardwareWallets) -> Subscription { - hws.refresh().map(Message::HardwareWallets) - } - - fn apply(&mut self, ctx: &mut Context) -> bool { - ctx.bitcoin_config.network = self.network; - // Drop connections to hardware wallets. - self.hw_xpubs = Vec::new(); - true - } - - fn view<'a>(&'a self, hws: &'a HardwareWallets, progress: (usize, usize)) -> Element { - view::participate_xpub( - progress, - hws.list - .iter() - .enumerate() - .map(|(i, hw)| { - if let Some(hw_xpubs) = self - .hw_xpubs - .iter() - .find(|h| hw.fingerprint() == Some(h.fingerprint)) - { - view::hardware_wallet_xpubs( - i, - hw, - Some(&hw_xpubs.xpubs), - hw_xpubs.processing, - hw_xpubs.error.as_ref(), - ) - } else { - view::hardware_wallet_xpubs(i, hw, None, false, None) - } - }) - .collect(), - self.xpubs_signer.view(), - self.shared, - ) - } -} - -impl From for Box { - fn from(s: ParticipateXpub) -> Box { - Box::new(s) - } -} - pub struct ImportDescriptor { network: Network, imported_descriptor: form::Value, diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 34024a20..ed92782d 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -1,16 +1,16 @@ mod bitcoind; mod descriptor; mod mnemonic; +mod share_xpubs; pub use bitcoind::{ DefineBitcoind, DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep, }; -pub use descriptor::{ - BackupDescriptor, DefineDescriptor, ImportDescriptor, ParticipateXpub, RegisterDescriptor, -}; +pub use descriptor::{BackupDescriptor, DefineDescriptor, ImportDescriptor, RegisterDescriptor}; pub use mnemonic::{BackupMnemonic, RecoverMnemonic}; +pub use share_xpubs::ShareXpubs; use std::path::PathBuf; diff --git a/gui/src/installer/step/share_xpubs.rs b/gui/src/installer/step/share_xpubs.rs new file mode 100644 index 00000000..5a65d3bb --- /dev/null +++ b/gui/src/installer/step/share_xpubs.rs @@ -0,0 +1,192 @@ +use std::sync::{Arc, Mutex}; + +use iced::{Command, Subscription}; +use liana::miniscript::bitcoin::{ + bip32::{ChildNumber, Fingerprint}, + Network, +}; + +use liana_ui::widget::Element; + +use crate::{ + hw::{HardwareWallet, HardwareWallets}, + installer::{ + message::Message, + step::{ + descriptor::{default_derivation_path, get_extended_pubkey}, + Context, Step, + }, + view, Error, + }, + signer::Signer, +}; + +pub struct HardwareWalletXpubs { + fingerprint: Fingerprint, + xpubs: Vec, + processing: bool, + error: Option, +} + +pub struct SignerXpubs { + signer: Arc>, + xpubs: Vec, + next_account: ChildNumber, +} + +impl SignerXpubs { + fn new(signer: Arc>) -> Self { + Self { + signer, + xpubs: Vec::new(), + next_account: ChildNumber::from_hardened_idx(0).unwrap(), + } + } + + fn select(&mut self, network: Network) { + self.next_account = self.next_account.increment().unwrap(); + let signer = self.signer.lock().unwrap(); + let derivation_path = default_derivation_path(network); + // We keep only one for the moment. + self.xpubs = vec![format!( + "[{}{}]{}", + signer.fingerprint(), + derivation_path.to_string().trim_start_matches('m'), + signer.get_extended_pubkey(&derivation_path) + )]; + } + + pub fn view(&self) -> Element { + view::signer_xpubs(&self.xpubs) + } +} + +pub struct ShareXpubs { + network: Network, + + shared: bool, + + hw_xpubs: Vec, + xpubs_signer: SignerXpubs, +} + +impl ShareXpubs { + pub fn new(network: Network, signer: Arc>) -> Self { + Self { + network, + hw_xpubs: Vec::new(), + shared: false, + xpubs_signer: SignerXpubs::new(signer), + } + } +} + +impl Step for ShareXpubs { + // form value is set as valid each time it is edited. + // Verification of the values is happening when the user click on Next button. + fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command { + match message { + Message::UserActionDone(shared) => self.shared = shared, + Message::ImportXpub(fg, res) => { + if let Some(hw_xpubs) = self.hw_xpubs.iter_mut().find(|x| x.fingerprint == fg) { + hw_xpubs.processing = false; + match res { + Err(e) => { + hw_xpubs.error = e.into(); + } + Ok(xpub) => { + hw_xpubs.error = None; + // We keep only one for the moment. + hw_xpubs.xpubs = vec![xpub.to_string()]; + } + } + } + } + Message::UseHotSigner => { + self.xpubs_signer.select(self.network); + } + Message::Select(i) => { + if let Some(HardwareWallet::Supported { + device, + fingerprint, + .. + }) = hws.list.get(i) + { + let device = device.clone(); + let fingerprint = *fingerprint; + let network = self.network; + if let Some(hw_xpubs) = self + .hw_xpubs + .iter_mut() + .find(|x| x.fingerprint == fingerprint) + { + hw_xpubs.processing = true; + hw_xpubs.error = None; + } else { + self.hw_xpubs.push(HardwareWalletXpubs { + fingerprint, + xpubs: Vec::new(), + processing: true, + error: None, + }); + } + return Command::perform( + async move { + ( + fingerprint, + get_extended_pubkey(device, fingerprint, network).await, + ) + }, + |(fingerprint, res)| Message::ImportXpub(fingerprint, res), + ); + } + } + _ => {} + }; + Command::none() + } + + fn subscription(&self, hws: &HardwareWallets) -> Subscription { + hws.refresh().map(Message::HardwareWallets) + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + ctx.bitcoin_config.network = self.network; + // Drop connections to hardware wallets. + self.hw_xpubs = Vec::new(); + true + } + + fn view<'a>(&'a self, hws: &'a HardwareWallets, _progress: (usize, usize)) -> Element { + view::share_xpubs( + hws.list + .iter() + .enumerate() + .map(|(i, hw)| { + if let Some(hw_xpubs) = self + .hw_xpubs + .iter() + .find(|h| hw.fingerprint() == Some(h.fingerprint)) + { + view::hardware_wallet_xpubs( + i, + hw, + Some(&hw_xpubs.xpubs), + hw_xpubs.processing, + hw_xpubs.error.as_ref(), + ) + } else { + view::hardware_wallet_xpubs(i, hw, None, false, None) + } + }) + .collect(), + self.xpubs_signer.view(), + ) + } +} + +impl From for Box { + fn from(s: ShareXpubs) -> Box { + Box::new(s) + } +} diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index fa4eaee5..59ef5dfc 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -38,7 +38,32 @@ pub fn welcome<'a>() -> Element<'a, Message> { Container::new( Column::new() .push( - Container::new(image::liana_brand_grey().width(Length::Fixed(200.0))).padding(100), + Container::new( + Row::new() + .align_items(Alignment::Center) + .push( + Container::new(image::liana_brand_grey().width(Length::Fixed(200.0))) + .width(Length::Fill), + ) + .push( + Row::new() + .push( + button::secondary( + Some(icon::previous_icon()), + "Change network", + ) + .width(Length::Fixed(200.0)) + .on_press(Message::BackToLauncher), + ) + .push( + button::secondary(None, "Share Xpubs") + .width(Length::Fixed(200.0)) + .on_press(Message::ShareXpubs), + ) + .spacing(20), + ), + ) + .padding(100), ) .push( Container::new( @@ -69,28 +94,6 @@ pub fn welcome<'a>() -> Element<'a, Message> { ) .padding(20), ) - .push( - Container::new( - Column::new() - .spacing(20) - .align_items(Alignment::Center) - .push( - image::participate_in_new_wallet_icon() - .width(Length::Fixed(200.0)), - ) - .push( - p1_regular("Participate in new wallet") - .style(color::GREY_3), - ) - .push( - button::secondary(None, "Select") - .width(Length::Fixed(200.0)) - .on_press(Message::ParticipateWallet), - ) - .align_items(Alignment::Center), - ) - .padding(20), - ) .push( Container::new( Column::new() @@ -101,7 +104,8 @@ pub fn welcome<'a>() -> Element<'a, Message> { .width(Length::Fixed(100.0)), ) .push( - p1_regular("Restore a wallet").style(color::GREY_3), + p1_regular("Add an existing wallet") + .style(color::GREY_3), ) .push( button::secondary(None, "Select") @@ -113,11 +117,6 @@ pub fn welcome<'a>() -> Element<'a, Message> { .padding(20), ), ) - .push( - button::secondary(Some(icon::previous_icon()), "Change network") - .width(Length::Fixed(200.0)) - .on_press(Message::BackToLauncher), - ) .push(Space::with_height(Length::Fixed(100.0))) .spacing(50) .align_items(Alignment::Center), @@ -566,42 +565,24 @@ pub fn hardware_wallet_xpubs<'a>( .into() } -pub fn participate_xpub<'a>( - progress: (usize, usize), +pub fn share_xpubs<'a>( hws: Vec>, signer: Element<'a, Message>, - shared: bool, ) -> Element<'a, Message> { layout( - progress, + (0, 0), "Share your public keys", Column::new() .push( - Column::new() - .push( - Container::new( - text("Generate an extended public key by selecting a signing device:") - .bold(), - ) - .width(Length::Fill), - ) - .spacing(10) - .push(Column::with_children(hws).spacing(10)) - .push(signer) - .width(Length::Fill), + Container::new( + text("Generate an extended public key by selecting a signing device:").bold(), + ) + .width(Length::Fill), ) - .push( - checkbox("I have shared my extended public key", shared) - .on_toggle(Message::UserActionDone), - ) - .push(if shared { - button::primary(None, "Next") - .width(Length::Fixed(200.0)) - .on_press(Message::Next) - } else { - button::primary(None, "Next").width(Length::Fixed(200.0)) - }) - .spacing(50), + .spacing(10) + .push(Column::with_children(hws).spacing(10)) + .push(signer) + .width(Length::Fill), true, Some(Message::Previous), ) @@ -1951,11 +1932,15 @@ fn layout<'a>( .center_x(), ) .push(Container::new(h3(title)).width(Length::FillPortion(8))) - .push( - Container::new(text(format!("{} | {}", progress.0, progress.1))) - .width(Length::FillPortion(2)) - .center_x(), - ), + .push_maybe(if progress.1 > 0 { + Some( + Container::new(text(format!("{} | {}", progress.0, progress.1))) + .width(Length::FillPortion(2)) + .center_x(), + ) + } else { + None + }), ) .push( Row::new()