From a2ac34e6b03ef92025ac0f9c9cb709719ea6375a Mon Sep 17 00:00:00 2001 From: edouard Date: Thu, 19 Jan 2023 18:00:45 +0100 Subject: [PATCH] installer: Participate in a new wallet section --- gui/src/installer/message.rs | 4 +- gui/src/installer/mod.rs | 16 ++- gui/src/installer/step/descriptor.rs | 150 +++++++++++++++++++++++-- gui/src/installer/step/mod.rs | 4 +- gui/src/installer/view.rs | 159 +++++++++++++++++++++++++-- gui/src/ui/icon.rs | 4 + 6 files changed, 315 insertions(+), 22 deletions(-) diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index 9c24030a..6090308a 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -10,8 +10,9 @@ use crate::hw::HardwareWallet; #[derive(Debug, Clone)] pub enum Message { CreateWallet, + ParticipateWallet, ImportWallet, - BackupDone(bool), + UserActionDone(bool), Exit(PathBuf), Clibpboard(String), Next, @@ -24,6 +25,7 @@ pub enum Message { Network(Network), DefineBitcoind(DefineBitcoind), DefineDescriptor(DefineDescriptor), + ImportXpub(Result), ConnectedHardwareWallets(Vec), WalletRegistered(Result<(Fingerprint, Option<[u8; 32]>), Error>), } diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index 76630970..0c0df440 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -18,7 +18,7 @@ use crate::{ pub use message::Message; use step::{ BackupDescriptor, Context, DefineBitcoind, DefineDescriptor, Final, ImportDescriptor, - RegisterDescriptor, Step, Welcome, + ParticipateXpub, RegisterDescriptor, Step, Welcome, }; pub struct Installer { @@ -100,10 +100,22 @@ impl Installer { ]; self.next() } + Message::ParticipateWallet => { + self.steps = vec![ + Welcome::default().into(), + ParticipateXpub::new().into(), + ImportDescriptor::new(false).into(), + BackupDescriptor::default().into(), + RegisterDescriptor::default().into(), + DefineBitcoind::new().into(), + Final::new().into(), + ]; + self.next() + } Message::ImportWallet => { self.steps = vec![ Welcome::default().into(), - ImportDescriptor::new().into(), + ImportDescriptor::new(true).into(), RegisterDescriptor::default().into(), DefineBitcoind::new().into(), Final::new().into(), diff --git a/gui/src/installer/step/descriptor.rs b/gui/src/installer/step/descriptor.rs index 29ca0984..e32816b6 100644 --- a/gui/src/installer/step/descriptor.rs +++ b/gui/src/installer/step/descriptor.rs @@ -611,17 +611,156 @@ async fn get_extended_pubkey( })) } +pub struct ParticipateXpub { + network: Network, + network_valid: bool, + data_dir: Option, + + xpub: Option, + shared: bool, + + processing: bool, + chosen_hw: Option, + hws: Vec<(HardwareWallet, bool)>, + error: Option, +} + +impl ParticipateXpub { + pub fn new() -> Self { + Self { + network: Network::Bitcoin, + network_valid: true, + data_dir: None, + processing: false, + xpub: None, + chosen_hw: None, + hws: Vec::new(), + shared: false, + error: None, + } + } +} + +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, message: Message) -> Command { + match message { + Message::Network(network) => { + self.network = network; + let mut network_datadir = self.data_dir.clone().unwrap(); + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); + } + Message::UserActionDone(shared) => self.shared = shared, + Message::ImportXpub(res) => { + self.processing = false; + match res { + Err(e) => { + self.error = e.into(); + self.chosen_hw = None; + } + Ok(xpub) => { + self.error = None; + self.xpub = Some(xpub.to_string().trim_end_matches("/<0;1>/*").to_string()); + for (i, (_, imported)) in self.hws.iter_mut().enumerate() { + *imported = Some(i) == self.chosen_hw; + } + self.chosen_hw = None; + } + } + } + Message::Select(i) => { + if let Some((hw, _)) = self.hws.get(i) { + let device = hw.device.clone(); + self.chosen_hw = Some(i); + self.processing = true; + self.error = None; + return Command::perform( + get_extended_pubkey(device, hw.fingerprint, self.network), + Message::ImportXpub, + ); + } + } + Message::ConnectedHardwareWallets(hws) => { + for hw in hws { + if !self + .hws + .iter() + .any(|(h, _)| h.fingerprint == hw.fingerprint) + { + self.hws.push((hw, false)); + } + } + } + Message::Reload => { + return self.load(); + } + _ => {} + }; + Command::none() + } + + fn load_context(&mut self, ctx: &Context) { + self.network = ctx.bitcoin_config.network; + self.data_dir = Some(ctx.data_dir.clone()); + let mut network_datadir = ctx.data_dir.clone(); + network_datadir.push(self.network.to_string()); + self.network_valid = !network_datadir.exists(); + } + + fn load(&self) -> Command { + Command::perform( + list_hardware_wallets(&[], None), + Message::ConnectedHardwareWallets, + ) + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + ctx.bitcoin_config.network = self.network; + true + } + + fn view(&self, progress: (usize, usize)) -> Element { + view::participate_xpub( + progress, + self.network, + self.network_valid, + &self.hws, + self.processing, + self.chosen_hw, + self.xpub.as_ref(), + self.shared, + self.error.as_ref(), + ) + } +} + +impl Default for ParticipateXpub { + fn default() -> Self { + Self::new() + } +} + +impl From for Box { + fn from(s: ParticipateXpub) -> Box { + Box::new(s) + } +} + pub struct ImportDescriptor { network: Network, network_valid: bool, + change_network: bool, data_dir: Option, imported_descriptor: form::Value, error: Option, } impl ImportDescriptor { - pub fn new() -> Self { + pub fn new(change_network: bool) -> Self { Self { + change_network, network: Network::Bitcoin, network_valid: true, data_dir: None, @@ -679,6 +818,7 @@ impl Step for ImportDescriptor { fn view(&self, progress: (usize, usize)) -> Element { view::import_descriptor( progress, + self.change_network, self.network, self.network_valid, &self.imported_descriptor, @@ -687,12 +827,6 @@ impl Step for ImportDescriptor { } } -impl Default for ImportDescriptor { - fn default() -> Self { - Self::new() - } -} - impl From for Box { fn from(s: ImportDescriptor) -> Box { Box::new(s) @@ -817,7 +951,7 @@ pub struct BackupDescriptor { impl Step for BackupDescriptor { fn update(&mut self, message: Message) -> Command { - if let Message::BackupDone(done) = message { + if let Message::UserActionDone(done) = message { self.done = done; } Command::none() diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index b3a20ff1..f425d907 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -1,5 +1,7 @@ mod descriptor; -pub use descriptor::{BackupDescriptor, DefineDescriptor, ImportDescriptor, RegisterDescriptor}; +pub use descriptor::{ + BackupDescriptor, DefineDescriptor, ImportDescriptor, ParticipateXpub, RegisterDescriptor, +}; use std::path::PathBuf; use std::str::FromStr; diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 8af8a6b9..e77d6087 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -82,12 +82,12 @@ pub fn welcome<'a>() -> Element<'a, Message> { Button::new( Container::new( Column::new() - .width(Length::Units(200)) + .width(Length::Units(250)) .push(icon::wallet_icon().size(50).width(Length::Units(100))) - .push(text("Create new wallet")) + .push(text("Create a new wallet")) .align_items(Alignment::Center), ) - .padding(50), + .padding(20), ) .style(button::Style::Border.into()) .on_press(Message::CreateWallet), @@ -96,12 +96,26 @@ pub fn welcome<'a>() -> Element<'a, Message> { Button::new( Container::new( Column::new() - .width(Length::Units(200)) - .push(icon::import_icon().size(50).width(Length::Units(100))) - .push(text("Import wallet")) + .width(Length::Units(250)) + .push(icon::people_icon().size(50).width(Length::Units(100))) + .push(text("Participate in a new wallet")) .align_items(Alignment::Center), ) - .padding(50), + .padding(20), + ) + .style(button::Style::Border.into()) + .on_press(Message::ParticipateWallet), + ) + .push( + Button::new( + Container::new( + Column::new() + .width(Length::Units(250)) + .push(icon::import_icon().size(50).width(Length::Units(100))) + .push(text("Import a wallet backup")) + .align_items(Alignment::Center), + ) + .padding(20), ) .style(button::Style::Border.into()) .on_press(Message::ImportWallet), @@ -109,7 +123,6 @@ pub fn welcome<'a>() -> Element<'a, Message> { ) .width(Length::Fill) .height(Length::Fill) - .padding(100) .spacing(50) .align_items(Alignment::Center), )) @@ -327,6 +340,7 @@ pub fn define_descriptor<'a>( pub fn import_descriptor<'a>( progress: (usize, usize), + change_network: bool, network: bitcoin::Network, network_valid: bool, imported_descriptor: &form::Value, @@ -367,7 +381,11 @@ pub fn import_descriptor<'a>( .push( Column::new() .spacing(20) - .push(row_network) + .push_maybe(if change_network { + Some(row_network) + } else { + None + }) .push(col_descriptor), ) .push(if imported_descriptor.value.is_empty() { @@ -386,6 +404,127 @@ pub fn import_descriptor<'a>( ) } +#[allow(clippy::too_many_arguments)] +pub fn participate_xpub<'a>( + progress: (usize, usize), + network: bitcoin::Network, + network_valid: bool, + hws: &[(HardwareWallet, bool)], + processing: bool, + chosen_hw: Option, + xpub: Option<&'a String>, + shared: bool, + error: Option<&Error>, +) -> Element<'a, Message> { + let row_network = Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(text("Network:").bold()) + .push(Container::new( + PickList::new(&NETWORKS[..], Some(Network::from(network)), |net| { + Message::Network(net.into()) + }) + .padding(10), + )) + .push_maybe(if network_valid { + None + } else { + Some(card::warning( + "A data directory already exists for this network".to_string(), + )) + }); + + layout( + progress, + Column::new() + .push(text("Share your public key").bold().size(50)) + .push( + Column::new() + .spacing(20) + .width(Length::Fill) + .push(row_network), + ) + .push( + Column::new() + .push( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push( + Container::new( + text(format!("Select your hardware wallet:")).bold(), + ) + .width(Length::Fill), + ) + .push( + button::border(Some(icon::reload_icon()), "Refresh") + .on_press(Message::Reload), + ), + ) + .spacing(10) + .push( + hws.iter() + .enumerate() + .fold(Column::new().spacing(10), |col, (i, hw)| { + col.push(hw_list_view( + i, + &hw.0, + Some(i) == chosen_hw, + processing, + hw.1, + )) + }), + ) + .width(Length::Fill), + ) + .push_maybe(xpub.map(|xpub| { + Column::new() + .spacing(5) + .push(text("Your extended pubkey:").bold()) + .push( + Row::new() + .spacing(5) + .align_items(Alignment::Center) + .push( + Container::new( + Scrollable::new(Container::new(text(xpub)).padding(10)) + .horizontal_scroll( + Properties::new().width(2).scroller_width(2), + ), + ) + .width(Length::Fill), + ) + .push( + Container::new( + button::border(Some(icon::clipboard_icon()), "Copy") + .on_press(Message::Clibpboard(xpub.clone())) + .width(Length::Shrink), + ) + .padding(10), + ), + ) + })) + .push(Checkbox::new( + "I have shared my xpub", + shared, + Message::UserActionDone, + )) + .push(if shared { + button::primary(None, "Next") + .width(Length::Units(200)) + .on_press(Message::Next) + } else { + button::primary(None, "Next").width(Length::Units(200)) + }) + .push_maybe(error.map(|e| card::error("Hardware error", e.to_string()))) + .width(Length::Fill) + .height(Length::Fill) + .padding(100) + .spacing(50) + .align_items(Alignment::Center), + ) +} + pub fn register_descriptor<'a>( progress: (usize, usize), descriptor: String, @@ -518,7 +657,7 @@ pub fn backup_descriptor<'a>( .push(Checkbox::new( "I have backed up my descriptor", done, - Message::BackupDone, + Message::UserActionDone, )) .push(if done { button::primary(None, "Next") diff --git a/gui/src/ui/icon.rs b/gui/src/ui/icon.rs index 46d1a8a7..92141dc4 100644 --- a/gui/src/ui/icon.rs +++ b/gui/src/ui/icon.rs @@ -218,3 +218,7 @@ pub fn down_icon() -> Text<'static> { pub fn up_icon() -> Text<'static> { icon('\u{F27C}') } + +pub fn people_icon() -> Text<'static> { + icon('\u{F4CF}') +}