Move all network setup in launcher

This commit is contained in:
edouardparis 2024-05-29 12:19:35 +02:00
parent be768b1a67
commit 9ae33408cb
6 changed files with 293 additions and 402 deletions

View File

@ -1,7 +1,4 @@
use liana::miniscript::{ use liana::miniscript::{bitcoin::bip32::Fingerprint, DescriptorPublicKey};
bitcoin::{bip32::Fingerprint, Network},
DescriptorPublicKey,
};
use std::path::PathBuf; use std::path::PathBuf;
use super::Error; use super::Error;
@ -29,7 +26,6 @@ pub enum Message {
Select(usize), Select(usize),
UseHotSigner, UseHotSigner,
Installed(Result<PathBuf, Error>), Installed(Result<PathBuf, Error>),
Network(Network),
CreateTaprootDescriptor(bool), CreateTaprootDescriptor(bool),
SelectBitcoindType(SelectBitcoindTypeMsg), SelectBitcoindType(SelectBitcoindTypeMsg),
InternalBitcoind(InternalBitcoindMsg), InternalBitcoind(InternalBitcoindMsg),

View File

@ -29,6 +29,7 @@ use step::{
}; };
pub struct Installer { pub struct Installer {
network: bitcoin::Network,
current: usize, current: usize,
steps: Vec<Box<dyn Step>>, steps: Vec<Box<dyn Step>>,
hws: HardwareWallets, hws: HardwareWallets,
@ -61,6 +62,7 @@ impl Installer {
) -> (Installer, Command<Message>) { ) -> (Installer, Command<Message>) {
( (
Installer { Installer {
network,
current: 0, current: 0,
hws: HardwareWallets::new(destination_path.clone(), network), hws: HardwareWallets::new(destination_path.clone(), network),
steps: vec![Welcome::default().into()], steps: vec![Welcome::default().into()],
@ -135,7 +137,7 @@ impl Installer {
Message::CreateWallet => { Message::CreateWallet => {
self.steps = vec![ self.steps = vec![
Welcome::default().into(), Welcome::default().into(),
DefineDescriptor::new(self.signer.clone()).into(), DefineDescriptor::new(self.network, self.signer.clone()).into(),
BackupMnemonic::new(self.signer.clone()).into(), BackupMnemonic::new(self.signer.clone()).into(),
BackupDescriptor::default().into(), BackupDescriptor::default().into(),
RegisterDescriptor::new_create_wallet().into(), RegisterDescriptor::new_create_wallet().into(),
@ -149,8 +151,8 @@ impl Installer {
Message::ParticipateWallet => { Message::ParticipateWallet => {
self.steps = vec![ self.steps = vec![
Welcome::default().into(), Welcome::default().into(),
ParticipateXpub::new(self.signer.clone()).into(), ParticipateXpub::new(self.network, self.signer.clone()).into(),
ImportDescriptor::new(false).into(), ImportDescriptor::new(self.network).into(),
BackupMnemonic::new(self.signer.clone()).into(), BackupMnemonic::new(self.signer.clone()).into(),
RegisterDescriptor::new_import_wallet().into(), RegisterDescriptor::new_import_wallet().into(),
SelectBitcoindTypeStep::new().into(), SelectBitcoindTypeStep::new().into(),
@ -163,7 +165,7 @@ impl Installer {
Message::ImportWallet => { Message::ImportWallet => {
self.steps = vec![ self.steps = vec![
Welcome::default().into(), Welcome::default().into(),
ImportDescriptor::new(true).into(), ImportDescriptor::new(self.network).into(),
RecoverMnemonic::default().into(), RecoverMnemonic::default().into(),
RegisterDescriptor::new_import_wallet().into(), RegisterDescriptor::new_import_wallet().into(),
SelectBitcoindTypeStep::new().into(), SelectBitcoindTypeStep::new().into(),

View File

@ -1,6 +1,5 @@
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -196,11 +195,9 @@ impl Setup {
} }
pub struct DefineDescriptor { pub struct DefineDescriptor {
data_dir: Option<PathBuf>, setup: Setup,
setup: HashMap<Network, Setup>,
network: Network, network: Network,
network_valid: bool,
use_taproot: bool, use_taproot: bool,
modal: Option<Box<dyn DescriptorEditModal>>, modal: Option<Box<dyn DescriptorEditModal>>,
@ -210,13 +207,11 @@ pub struct DefineDescriptor {
} }
impl DefineDescriptor { impl DefineDescriptor {
pub fn new(signer: Arc<Mutex<Signer>>) -> Self { pub fn new(network: Network, signer: Arc<Mutex<Signer>>) -> Self {
Self { Self {
network: Network::Bitcoin, network,
use_taproot: false, use_taproot: false,
setup: HashMap::from([(Network::Bitcoin, Setup::new())]), setup: Setup::new(),
data_dir: None,
network_valid: true,
modal: None, modal: None,
signer, signer,
error: None, error: None,
@ -224,25 +219,10 @@ impl DefineDescriptor {
} }
fn valid(&self) -> bool { fn valid(&self) -> bool {
self.setup[&self.network].valid() self.setup.valid()
} }
fn setup_mut(&mut self) -> &mut Setup { fn setup_mut(&mut self) -> &mut Setup {
self.setup &mut self.setup
.get_mut(&self.network)
.expect("There is always one")
}
fn set_network(&mut self, network: Network) {
self.network = network;
if self.setup.get(&self.network).is_none() {
self.setup.insert(self.network, Setup::new());
}
self.signer.lock().unwrap().set_network(network);
if let Some(mut network_datadir) = self.data_dir.clone() {
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
}
self.check_setup()
} }
fn check_setup(&mut self) { fn check_setup(&mut self) {
@ -257,16 +237,11 @@ impl Step for DefineDescriptor {
// form value is set as valid each time it is edited. // form value is set as valid each time it is edited.
// Verification of the values is happening when the user click on Next button. // Verification of the values is happening when the user click on Next button.
fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command<Message> { fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command<Message> {
let network = self.network;
self.error = None; self.error = None;
match message { match message {
Message::Close => { Message::Close => {
self.modal = None; self.modal = None;
} }
Message::Network(network) => {
hws.set_network(network);
self.set_network(network)
}
Message::CreateTaprootDescriptor(use_taproot) => { Message::CreateTaprootDescriptor(use_taproot) => {
self.use_taproot = use_taproot; self.use_taproot = use_taproot;
self.check_setup(); self.check_setup();
@ -313,6 +288,7 @@ impl Step for DefineDescriptor {
} }
message::DefineKey::Edit => { message::DefineKey::Edit => {
let use_taproot = self.use_taproot; let use_taproot = self.use_taproot;
let network = self.network;
let setup = self.setup_mut(); let setup = self.setup_mut();
let modal = EditXpubModal::new( let modal = EditXpubModal::new(
use_taproot, use_taproot,
@ -424,7 +400,7 @@ impl Step for DefineDescriptor {
j, j,
self.network, self.network,
self.signer.clone(), self.signer.clone(),
self.setup[&self.network].keys.clone(), self.setup.keys.clone(),
); );
let cmd = modal.load(); let cmd = modal.load();
self.modal = Some(Box::new(modal)); self.modal = Some(Box::new(modal));
@ -459,11 +435,6 @@ impl Step for DefineDescriptor {
Command::none() Command::none()
} }
fn load_context(&mut self, ctx: &Context) {
self.data_dir = Some(ctx.data_dir.clone());
self.set_network(ctx.bitcoin_config.network)
}
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> { fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
if let Some(modal) = &self.modal { if let Some(modal) = &self.modal {
modal.subscription(hws) modal.subscription(hws)
@ -478,9 +449,10 @@ impl Step for DefineDescriptor {
let mut hw_is_used = false; let mut hw_is_used = false;
let mut spending_keys: Vec<DescriptorPublicKey> = Vec::new(); let mut spending_keys: Vec<DescriptorPublicKey> = Vec::new();
let mut key_derivation_index = HashMap::<Fingerprint, usize>::new(); let mut key_derivation_index = HashMap::<Fingerprint, usize>::new();
for spending_key in self.setup[&self.network].spending_keys.iter().clone() { for spending_key in self.setup.spending_keys.iter().clone() {
let fingerprint = spending_key.expect("Must be present at this step"); let fingerprint = spending_key.expect("Must be present at this step");
let key = self.setup[&self.network] let key = self
.setup
.keys .keys
.iter() .iter()
.find(|key| key.key.master_fingerprint() == fingerprint) .find(|key| key.key.master_fingerprint() == fingerprint)
@ -506,11 +478,12 @@ impl Step for DefineDescriptor {
let mut recovery_paths = BTreeMap::new(); let mut recovery_paths = BTreeMap::new();
for path in &self.setup[&self.network].recovery_paths { for path in &self.setup.recovery_paths {
let mut recovery_keys: Vec<DescriptorPublicKey> = Vec::new(); let mut recovery_keys: Vec<DescriptorPublicKey> = Vec::new();
for recovery_key in path.keys.iter().clone() { for recovery_key in path.keys.iter().clone() {
let fingerprint = recovery_key.expect("Must be present at this step"); let fingerprint = recovery_key.expect("Must be present at this step");
let key = self.setup[&self.network] let key = self
.setup
.keys .keys
.iter() .iter()
.find(|key| key.key.master_fingerprint() == fingerprint) .find(|key| key.key.master_fingerprint() == fingerprint)
@ -544,14 +517,14 @@ impl Step for DefineDescriptor {
recovery_paths.insert(path.sequence, recovery_keys); recovery_paths.insert(path.sequence, recovery_keys);
} }
if !self.network_valid || spending_keys.is_empty() { if spending_keys.is_empty() {
return false; return false;
} }
let spending_keys = if spending_keys.len() == 1 { let spending_keys = if spending_keys.len() == 1 {
PathInfo::Single(spending_keys[0].clone()) PathInfo::Single(spending_keys[0].clone())
} else { } else {
PathInfo::Multi(self.setup[&self.network].spending_threshold, spending_keys) PathInfo::Multi(self.setup.spending_threshold, spending_keys)
}; };
let policy = match if self.use_taproot { let policy = match if self.use_taproot {
@ -576,13 +549,11 @@ impl Step for DefineDescriptor {
hws: &'a HardwareWallets, hws: &'a HardwareWallets,
progress: (usize, usize), progress: (usize, usize),
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let aliases = self.setup[&self.network].keys_aliases(); let aliases = self.setup.keys_aliases();
let content = view::define_descriptor( let content = view::define_descriptor(
progress, progress,
self.network,
self.network_valid,
self.use_taproot, self.use_taproot,
self.setup[&self.network] self.setup
.spending_keys .spending_keys
.iter() .iter()
.enumerate() .enumerate()
@ -590,10 +561,8 @@ impl Step for DefineDescriptor {
if let Some(key) = key { if let Some(key) = key {
view::defined_descriptor_key( view::defined_descriptor_key(
aliases.get(key).unwrap().to_string(), aliases.get(key).unwrap().to_string(),
self.setup[&self.network].duplicate_name.contains(key), self.setup.duplicate_name.contains(key),
self.setup[&self.network] self.setup.incompatible_with_tapminiscript.contains(key),
.incompatible_with_tapminiscript
.contains(key),
) )
} else { } else {
view::undefined_descriptor_key() view::undefined_descriptor_key()
@ -605,16 +574,16 @@ impl Step for DefineDescriptor {
}) })
}) })
.collect(), .collect(),
self.setup[&self.network].spending_threshold, self.setup.spending_threshold,
self.setup[&self.network] self.setup
.recovery_paths .recovery_paths
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, path)| { .map(|(i, path)| {
path.view( path.view(
&aliases, &aliases,
&self.setup[&self.network].duplicate_name, &self.setup.duplicate_name,
&self.setup[&self.network].incompatible_with_tapminiscript, &self.setup.incompatible_with_tapminiscript,
) )
.map(move |msg| { .map(move |msg| {
Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath(i, msg)) Message::DefineDescriptor(message::DefineDescriptor::RecoveryPath(i, msg))
@ -1133,13 +1102,6 @@ pub struct HardwareWalletXpubs {
error: Option<Error>, error: Option<Error>,
} }
impl HardwareWalletXpubs {
fn reset(&mut self) {
self.error = None;
self.xpubs = Vec::new();
}
}
pub struct SignerXpubs { pub struct SignerXpubs {
signer: Arc<Mutex<Signer>>, signer: Arc<Mutex<Signer>>,
xpubs: Vec<String>, xpubs: Vec<String>,
@ -1155,11 +1117,6 @@ impl SignerXpubs {
} }
} }
fn reset(&mut self) {
self.xpubs = Vec::new();
self.next_account = ChildNumber::from_hardened_idx(0).unwrap();
}
fn select(&mut self, network: Network) { fn select(&mut self, network: Network) {
self.next_account = self.next_account.increment().unwrap(); self.next_account = self.next_account.increment().unwrap();
let signer = self.signer.lock().unwrap(); let signer = self.signer.lock().unwrap();
@ -1180,8 +1137,6 @@ impl SignerXpubs {
pub struct ParticipateXpub { pub struct ParticipateXpub {
network: Network, network: Network,
network_valid: bool,
data_dir: Option<PathBuf>,
shared: bool, shared: bool,
@ -1190,33 +1145,14 @@ pub struct ParticipateXpub {
} }
impl ParticipateXpub { impl ParticipateXpub {
pub fn new(signer: Arc<Mutex<Signer>>) -> Self { pub fn new(network: Network, signer: Arc<Mutex<Signer>>) -> Self {
Self { Self {
network: Network::Bitcoin, network,
network_valid: true,
data_dir: None,
hw_xpubs: Vec::new(), hw_xpubs: Vec::new(),
shared: false, shared: false,
xpubs_signer: SignerXpubs::new(signer), xpubs_signer: SignerXpubs::new(signer),
} }
} }
fn set_network(&mut self, network: Network) {
if network != self.network {
self.hw_xpubs.iter_mut().for_each(|hw| hw.reset());
self.xpubs_signer.reset();
}
self.network = network;
self.xpubs_signer
.signer
.lock()
.unwrap()
.set_network(network);
if let Some(mut network_datadir) = self.data_dir.clone() {
network_datadir.push(self.network.to_string());
self.network_valid = !network_datadir.exists();
}
}
} }
impl Step for ParticipateXpub { impl Step for ParticipateXpub {
@ -1224,10 +1160,6 @@ impl Step for ParticipateXpub {
// Verification of the values is happening when the user click on Next button. // Verification of the values is happening when the user click on Next button.
fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command<Message> { fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Command<Message> {
match message { match message {
Message::Network(network) => {
hws.set_network(network);
self.set_network(network);
}
Message::UserActionDone(shared) => self.shared = shared, Message::UserActionDone(shared) => self.shared = shared,
Message::ImportXpub(fg, res) => { Message::ImportXpub(fg, res) => {
if let Some(hw_xpubs) = self.hw_xpubs.iter_mut().find(|x| x.fingerprint == fg) { if let Some(hw_xpubs) = self.hw_xpubs.iter_mut().find(|x| x.fingerprint == fg) {
@ -1292,11 +1224,6 @@ impl Step for ParticipateXpub {
hws.refresh().map(Message::HardwareWallets) hws.refresh().map(Message::HardwareWallets)
} }
fn load_context(&mut self, ctx: &Context) {
self.data_dir = Some(ctx.data_dir.clone());
self.set_network(ctx.bitcoin_config.network);
}
fn apply(&mut self, ctx: &mut Context) -> bool { fn apply(&mut self, ctx: &mut Context) -> bool {
ctx.bitcoin_config.network = self.network; ctx.bitcoin_config.network = self.network;
// Drop connections to hardware wallets. // Drop connections to hardware wallets.
@ -1307,8 +1234,6 @@ impl Step for ParticipateXpub {
fn view<'a>(&'a self, hws: &'a HardwareWallets, progress: (usize, usize)) -> Element<Message> { fn view<'a>(&'a self, hws: &'a HardwareWallets, progress: (usize, usize)) -> Element<Message> {
view::participate_xpub( view::participate_xpub(
progress, progress,
self.network,
self.network_valid,
hws.list hws.list
.iter() .iter()
.enumerate() .enumerate()
@ -1344,21 +1269,15 @@ impl From<ParticipateXpub> for Box<dyn Step> {
pub struct ImportDescriptor { pub struct ImportDescriptor {
network: Network, network: Network,
network_valid: bool,
change_network: bool,
data_dir: Option<PathBuf>,
imported_descriptor: form::Value<String>, imported_descriptor: form::Value<String>,
wrong_network: bool, wrong_network: bool,
error: Option<String>, error: Option<String>,
} }
impl ImportDescriptor { impl ImportDescriptor {
pub fn new(change_network: bool) -> Self { pub fn new(network: Network) -> Self {
Self { Self {
change_network, network,
network: Network::Bitcoin,
network_valid: true,
data_dir: None,
imported_descriptor: form::Value::default(), imported_descriptor: form::Value::default(),
wrong_network: false, wrong_network: false,
error: None, error: None,
@ -1397,32 +1316,13 @@ impl Step for ImportDescriptor {
// form value is set as valid each time it is edited. // form value is set as valid each time it is edited.
// Verification of the values is happening when the user click on Next button. // Verification of the values is happening when the user click on Next button.
fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command<Message> { fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command<Message> {
match message { if let Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) =
Message::Network(network) => { message
self.network = network; {
let mut network_datadir = self.data_dir.clone().unwrap(); self.imported_descriptor.value = desc;
network_datadir.push(self.network.to_string()); self.check_descriptor(self.network);
self.network_valid = !network_datadir.exists();
self.check_descriptor(self.network);
}
Message::DefineDescriptor(message::DefineDescriptor::ImportDescriptor(desc)) => {
self.imported_descriptor.value = desc;
self.check_descriptor(self.network);
}
_ => {}
};
Command::none()
}
fn load_context(&mut self, ctx: &Context) {
if ctx.bitcoin_config.network != self.network {
self.check_descriptor(ctx.bitcoin_config.network);
} }
self.network = ctx.bitcoin_config.network; Command::none()
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 apply(&mut self, ctx: &mut Context) -> bool { fn apply(&mut self, ctx: &mut Context) -> bool {
@ -1441,9 +1341,6 @@ impl Step for ImportDescriptor {
fn view(&self, _hws: &HardwareWallets, progress: (usize, usize)) -> Element<Message> { fn view(&self, _hws: &HardwareWallets, progress: (usize, usize)) -> Element<Message> {
view::import_descriptor( view::import_descriptor(
progress, progress,
self.change_network,
self.network,
self.network_valid,
&self.imported_descriptor, &self.imported_descriptor,
self.wrong_network, self.wrong_network,
self.error.as_ref(), self.error.as_ref(),
@ -1650,6 +1547,7 @@ impl From<BackupDescriptor> for Box<dyn Step> {
mod tests { mod tests {
use super::*; use super::*;
use iced_runtime::command::Action; use iced_runtime::command::Action;
use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub struct Sandbox<S: Step> { pub struct Sandbox<S: Step> {
@ -1686,9 +1584,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_define_descriptor_use_hotkey() { async fn test_define_descriptor_use_hotkey() {
let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap()); let mut ctx = Context::new(Network::Signet, PathBuf::from_str("/").unwrap());
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(Arc::new( let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(
Mutex::new(Signer::generate(Network::Bitcoin).unwrap()), Network::Bitcoin,
))); Arc::new(Mutex::new(Signer::generate(Network::Bitcoin).unwrap())),
));
// Edit primary key // Edit primary key
sandbox sandbox
@ -1767,9 +1666,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_define_descriptor_stores_if_hw_is_used() { async fn test_define_descriptor_stores_if_hw_is_used() {
let mut ctx = Context::new(Network::Testnet, PathBuf::from_str("/").unwrap()); let mut ctx = Context::new(Network::Testnet, PathBuf::from_str("/").unwrap());
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(Arc::new( let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(
Mutex::new(Signer::generate(Network::Testnet).unwrap()), Network::Testnet,
))); Arc::new(Mutex::new(Signer::generate(Network::Testnet).unwrap())),
));
sandbox.load(&ctx).await; sandbox.load(&ctx).await;
let specter_key = message::DefinePath::Key( let specter_key = message::DefinePath::Key(

View File

@ -32,55 +32,6 @@ use crate::{
}, },
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Network {
Mainnet,
Testnet,
Regtest,
Signet,
}
impl From<bitcoin::Network> for Network {
fn from(n: bitcoin::Network) -> Self {
match n {
bitcoin::Network::Bitcoin => Network::Mainnet,
bitcoin::Network::Testnet => Network::Testnet,
bitcoin::Network::Regtest => Network::Regtest,
bitcoin::Network::Signet => Network::Signet,
_ => Network::Mainnet,
}
}
}
impl From<Network> for bitcoin::Network {
fn from(network: Network) -> bitcoin::Network {
match network {
Network::Mainnet => bitcoin::Network::Bitcoin,
Network::Testnet => bitcoin::Network::Testnet,
Network::Regtest => bitcoin::Network::Regtest,
Network::Signet => bitcoin::Network::Signet,
}
}
}
impl std::fmt::Display for Network {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Mainnet => write!(f, "Bitcoin mainnet"),
Self::Testnet => write!(f, "Bitcoin testnet"),
Self::Regtest => write!(f, "Bitcoin regtest"),
Self::Signet => write!(f, "Bitcoin signet"),
}
}
}
const NETWORKS: [Network; 4] = [
Network::Mainnet,
Network::Testnet,
Network::Signet,
Network::Regtest,
];
pub fn welcome<'a>() -> Element<'a, Message> { pub fn welcome<'a>() -> Element<'a, Message> {
Container::new( Container::new(
Column::new() Column::new()
@ -191,31 +142,7 @@ impl std::fmt::Display for DescriptorKind {
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn define_descriptor_advanced_settings<'a>( pub fn define_descriptor_advanced_settings<'a>(use_taproot: bool) -> Element<'a, Message> {
network: bitcoin::Network,
network_valid: bool,
use_taproot: bool,
) -> Element<'a, Message> {
let col_network = Column::new()
.spacing(10)
.push(text("Network").bold())
.push(container(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Secondary
} else {
theme::PickList::Invalid
})
.padding(10),
))
.push_maybe(if network_valid {
None
} else {
Some(text("A data directory already exists for this network").style(color::RED))
});
let col_wallet = Column::new() let col_wallet = Column::new()
.spacing(10) .spacing(10)
.push(text("Descriptor type").bold()) .push(text("Descriptor type").bold())
@ -238,12 +165,7 @@ pub fn define_descriptor_advanced_settings<'a>(
.spacing(20) .spacing(20)
.push(Space::with_height(0)) .push(Space::with_height(0))
.push(separation().width(500)) .push(separation().width(500))
.push( .push(Row::new().push(col_wallet))
Row::new()
.push(col_network)
.push(Space::with_width(100))
.push(col_wallet),
)
.push_maybe(if use_taproot { .push_maybe(if use_taproot {
Some( Some(
p1_regular("Taproot is only supported by Liana version 5.0 and above") p1_regular("Taproot is only supported by Liana version 5.0 and above")
@ -259,8 +181,6 @@ pub fn define_descriptor_advanced_settings<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn define_descriptor<'a>( pub fn define_descriptor<'a>(
progress: (usize, usize), progress: (usize, usize),
network: bitcoin::Network,
network_valid: bool,
use_taproot: bool, use_taproot: bool,
spending_keys: Vec<Element<'a, Message>>, spending_keys: Vec<Element<'a, Message>>,
spending_threshold: usize, spending_threshold: usize,
@ -329,34 +249,29 @@ pub fn define_descriptor<'a>(
progress, progress,
"Create the wallet", "Create the wallet",
Column::new() Column::new()
.push( .push(collapse::Collapse::new(
collapse::Collapse::new( || {
|| { Button::new(
Button::new( Row::new()
Row::new() .align_items(Alignment::Center)
.align_items(Alignment::Center) .spacing(10)
.spacing(10) .push(text("Advanced settings").small().bold())
.push(text("Advanced settings").small().bold()) .push(icon::collapse_icon()),
.push(icon::collapse_icon()), )
) .style(theme::Button::Transparent)
.style(theme::Button::Transparent) },
}, || {
|| { Button::new(
Button::new( Row::new()
Row::new() .align_items(Alignment::Center)
.align_items(Alignment::Center) .spacing(10)
.spacing(10) .push(text("Advanced settings").small().bold())
.push(text("Advanced settings").small().bold()) .push(icon::collapsed_icon()),
.push(icon::collapsed_icon()), )
) .style(theme::Button::Transparent)
.style(theme::Button::Transparent) },
}, move || define_descriptor_advanced_settings(use_taproot),
move || { ))
define_descriptor_advanced_settings(network, network_valid, use_taproot)
},
)
.collapsed(!network_valid),
)
.push( .push(
Column::new() Column::new()
.width(Length::Fill) .width(Length::Fill)
@ -384,7 +299,7 @@ pub fn define_descriptor<'a>(
)) ))
.width(Length::Fixed(200.0)), .width(Length::Fixed(200.0)),
) )
.push(if !valid || !network_valid { .push(if !valid {
button::primary(None, "Next").width(Length::Fixed(200.0)) button::primary(None, "Next").width(Length::Fixed(200.0))
} else { } else {
button::primary(None, "Next") button::primary(None, "Next")
@ -455,33 +370,10 @@ pub fn recovery_path_view(
pub fn import_descriptor<'a>( pub fn import_descriptor<'a>(
progress: (usize, usize), progress: (usize, usize),
change_network: bool,
network: bitcoin::Network,
network_valid: bool,
imported_descriptor: &form::Value<String>, imported_descriptor: &form::Value<String>,
wrong_network: bool, wrong_network: bool,
error: Option<&String>, error: Option<&String>,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let row_network = Row::new()
.spacing(10)
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Simple
} else {
theme::PickList::Invalid
})
.padding(10),
))
.push_maybe(if network_valid {
None
} else {
Some(text("A data directory already exists for this network").style(color::RED))
});
let col_descriptor = Column::new() let col_descriptor = Column::new()
.push(text("Descriptor:").bold()) .push(text("Descriptor:").bold())
.push( .push(
@ -501,28 +393,13 @@ pub fn import_descriptor<'a>(
progress, progress,
"Import the wallet", "Import the wallet",
Column::new() Column::new()
.push( .push(Column::new().spacing(20).push(col_descriptor).push(text(
Column::new() "After creating the wallet, \
.spacing(20)
.push_maybe(if change_network {
Some(row_network)
} else {
None
})
.push(col_descriptor)
.push_maybe(if change_network {
// only show message when importing a descriptor
Some(text(
"After creating the wallet, \
you will need to perform a rescan of \ you will need to perform a rescan of \
the blockchain in order to see your \ the blockchain in order to see your \
coins and past transactions. This can \ coins and past transactions. This can \
be done in Settings > Bitcoin Core.", be done in Settings > Bitcoin Core.",
)) )))
} else {
None
}),
)
.push( .push(
if imported_descriptor.value.is_empty() || !imported_descriptor.valid { if imported_descriptor.value.is_empty() || !imported_descriptor.valid {
button::primary(None, "Next").width(Length::Fixed(200.0)) button::primary(None, "Next").width(Length::Fixed(200.0))
@ -684,43 +561,14 @@ pub fn hardware_wallet_xpubs<'a>(
pub fn participate_xpub<'a>( pub fn participate_xpub<'a>(
progress: (usize, usize), progress: (usize, usize),
network: bitcoin::Network,
network_valid: bool,
hws: Vec<Element<'a, Message>>, hws: Vec<Element<'a, Message>>,
signer: Element<'a, Message>, signer: Element<'a, Message>,
shared: bool, shared: bool,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let row_network = Row::new()
.spacing(10)
.align_items(Alignment::Center)
.push(text("Network:").bold())
.push(Container::new(
pick_list(&NETWORKS[..], Some(Network::from(network)), |net| {
Message::Network(net.into())
})
.style(if network_valid {
theme::PickList::Simple
} else {
theme::PickList::Invalid
})
.padding(10),
))
.push_maybe(if network_valid {
None
} else {
Some(text("A data directory already exists for this network").style(color::RED))
});
layout( layout(
progress, progress,
"Share your public keys", "Share your public keys",
Column::new() Column::new()
.push(
Column::new()
.spacing(20)
.width(Length::Fill)
.push(row_network),
)
.push( .push(
Column::new() Column::new()
.push( .push(
@ -739,7 +587,7 @@ pub fn participate_xpub<'a>(
checkbox("I have shared my extended public key", shared) checkbox("I have shared my extended public key", shared)
.on_toggle(Message::UserActionDone), .on_toggle(Message::UserActionDone),
) )
.push(if shared && network_valid { .push(if shared {
button::primary(None, "Next") button::primary(None, "Next")
.width(Length::Fixed(200.0)) .width(Length::Fixed(200.0))
.on_press(Message::Next) .on_press(Message::Next)

View File

@ -2,7 +2,7 @@ use std::path::PathBuf;
use iced::{ use iced::{
alignment::Horizontal, alignment::Horizontal,
widget::{tooltip, Space}, widget::{scrollable, tooltip},
Alignment, Command, Length, Subscription, Alignment, Command, Length, Subscription,
}; };
@ -28,33 +28,37 @@ fn wallet_name(network: &Network) -> String {
} }
pub struct Launcher { pub struct Launcher {
choices: Vec<Network>, // true if installed
choices: Vec<(Network, bool)>,
datadir_path: PathBuf, datadir_path: PathBuf,
error: Option<String>, error: Option<String>,
delete_wallet_modal: Option<DeleteWalletModal>, delete_wallet_modal: Option<DeleteWalletModal>,
collapsed: bool,
} }
impl Launcher { impl Launcher {
pub fn new(datadir_path: PathBuf) -> Self { pub fn new(datadir_path: PathBuf) -> Self {
let mut choices = Vec::new();
for network in [
Network::Bitcoin,
Network::Testnet,
Network::Signet,
Network::Regtest,
] {
if datadir_path.join(network.to_string()).exists() {
choices.push(network)
}
}
Self { Self {
choices: [
Network::Bitcoin,
Network::Testnet,
Network::Signet,
Network::Regtest,
]
.iter()
.map(|net| (*net, datadir_path.join(net.to_string()).exists()))
.collect(),
datadir_path, datadir_path,
choices,
error: None, error: None,
delete_wallet_modal: None, delete_wallet_modal: None,
collapsed: false,
} }
} }
fn is_fresh_install(&self) -> bool {
!self.choices.iter().any(|(_, installed)| *installed)
}
pub fn stop(&mut self) {} pub fn stop(&mut self) {}
pub fn subscription(&self) -> Subscription<Message> { pub fn subscription(&self) -> Subscription<Message> {
@ -63,9 +67,15 @@ impl Launcher {
pub fn update(&mut self, message: Message) -> Command<Message> { pub fn update(&mut self, message: Message) -> Command<Message> {
match message { match message {
Message::View(ViewMessage::StartInstall) => { Message::View(ViewMessage::ShowUninstalledNetworks) => {
self.collapsed = true;
Command::none()
}
Message::View(ViewMessage::StartInstall(net)) => {
let datadir_path = self.datadir_path.clone(); let datadir_path = self.datadir_path.clone();
Command::perform(async move { datadir_path }, Message::Install) Command::perform(async move { (datadir_path, net) }, |(d, n)| {
Message::Install(d, n)
})
} }
Message::View(ViewMessage::Check(network)) => Command::perform( Message::View(ViewMessage::Check(network)) => Command::perform(
check_network_datadir(self.datadir_path.clone(), network), check_network_datadir(self.datadir_path.clone(), network),
@ -88,11 +98,9 @@ impl Launcher {
} }
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => { Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => {
if let Some(modal) = &self.delete_wallet_modal { if let Some(modal) = &self.delete_wallet_modal {
let choices = self.choices.clone(); if let Some(choice) = self.choices.iter_mut().find(|c| c.0 == modal.network) {
self.choices = choices choice.1 = false;
.into_iter() }
.filter(|c| c != &modal.network)
.collect();
} }
Command::none() Command::none()
} }
@ -126,7 +134,7 @@ impl Launcher {
} }
pub fn view(&self) -> Element<Message> { pub fn view(&self) -> Element<Message> {
let content = Into::<Element<ViewMessage>>::into( let content = Into::<Element<ViewMessage>>::into(scrollable(
Column::new() Column::new()
.push( .push(
Container::new(image::liana_brand_grey().width(Length::Fixed(200.0))) Container::new(image::liana_brand_grey().width(Length::Fixed(200.0)))
@ -136,17 +144,107 @@ impl Launcher {
Container::new( Container::new(
Column::new() Column::new()
.spacing(30) .spacing(30)
.push(text("Welcome back").size(50).bold()) .push(if !self.is_fresh_install() {
text("Welcome back").size(50).bold()
} else {
text("Welcome").size(50).bold()
})
.push_maybe(self.error.as_ref().map(|e| card::simple(text(e)))) .push_maybe(self.error.as_ref().map(|e| card::simple(text(e))))
.push( .push(if self.is_fresh_install() {
self.choices Column::new()
.iter() .spacing(10)
.fold( .push(
Column::new() Button::new(
.push(text("Select network:").small().bold()) Row::new()
.spacing(10), .spacing(20)
|col, choice| { .align_items(Alignment::Center)
col.push( .push(
badge::Badge::new(icon::bitcoin_icon())
.style(theme::Badge::Bitcoin),
)
.push(text(format!(
"Create wallet on {}",
wallet_name(&Network::Bitcoin)
))),
)
.on_press(ViewMessage::StartInstall(Network::Bitcoin))
.padding(10)
.width(Length::Fixed(400.0))
.style(theme::Button::Border),
)
.push(if !self.collapsed {
Column::new().push(
Button::new(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push(badge::Badge::new(icon::plus_icon()))
.push(text("Create wallet on another network")),
)
.on_press(ViewMessage::ShowUninstalledNetworks)
.padding(10)
.width(Length::Fixed(400.0))
.style(theme::Button::TransparentBorder),
)
} else {
self.choices
.iter()
.filter_map(|(net, installed)| {
if *installed || *net == Network::Bitcoin {
None
} else {
Some(net)
}
})
.fold(Column::new().spacing(10), |col, choice| {
col.push(
Button::new(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push(
badge::Badge::new(
icon::bitcoin_icon(),
)
.style(theme::Badge::Standard),
)
.push(text(format!(
"Create wallet on {}",
wallet_name(choice)
))),
)
.on_press(ViewMessage::StartInstall(*choice))
.padding(10)
.width(Length::Fixed(400.0))
.style(theme::Button::Border),
)
})
})
} else {
Column::new()
.spacing(10)
.push(
self.choices
.iter()
.filter_map(
|(net, installed)| {
if *installed {
Some(net)
} else {
None
}
},
)
.fold(
Column::new()
.push(
text("Open an existing wallet:")
.small()
.bold(),
)
.spacing(10),
|col, choice| {
col.push(
Row::new() Row::new()
.spacing(10) .spacing(10)
.push( .push(
@ -169,7 +267,7 @@ impl Launcher {
) )
.on_press(ViewMessage::Check(*choice)) .on_press(ViewMessage::Check(*choice))
.padding(10) .padding(10)
.width(Length::Fill) .width(Length::Fixed(400.0))
.style(theme::Button::Border), .style(theme::Button::Border),
) )
.push(tooltip::Tooltip::new( .push(tooltip::Tooltip::new(
@ -187,35 +285,89 @@ impl Launcher {
)) ))
.align_items(Alignment::Center), .align_items(Alignment::Center),
) )
}, },
),
) )
.push( .push(
Button::new( if !self.collapsed
Row::new() && self.choices.iter().any(|(_, installed)| !installed)
.spacing(20) {
.align_items(Alignment::Center) Column::new().push(
.push(badge::Badge::new(icon::plus_icon())) Button::new(
.push(text("Install Liana on another network")), Row::new()
) .spacing(20)
.on_press(ViewMessage::StartInstall) .align_items(Alignment::Center)
.padding(10) .push(badge::Badge::new(icon::plus_icon()))
.width(Length::Fill) .push(text("Create a new wallet")),
.style(theme::Button::TransparentBorder), )
), .on_press(ViewMessage::ShowUninstalledNetworks)
) .padding(10)
.max_width(500) .width(Length::Fixed(400.0))
.align_items(Alignment::Center), .style(theme::Button::TransparentBorder),
)
} else if self.collapsed {
self.choices
.iter()
.filter_map(|(net, installed)| {
if *installed {
None
} else {
Some(net)
}
})
.fold(
Column::new()
.push(
text("Create a new wallet:")
.small()
.bold(),
)
.spacing(10),
|col, choice| {
col.push(
Button::new(
Row::new()
.spacing(20)
.align_items(Alignment::Center)
.push(
badge::Badge::new(
icon::bitcoin_icon(),
)
.style(match choice {
Network::Bitcoin => {
theme::Badge::Bitcoin
}
_ => theme::Badge::Standard,
}),
)
.push(text(wallet_name(choice))),
)
.on_press(ViewMessage::StartInstall(*choice))
.padding(10)
.width(Length::Fixed(400.0))
.style(theme::Button::Border),
)
},
)
} else {
Column::new()
},
)
})
.align_items(if self.is_fresh_install() {
Alignment::Center
} else {
Alignment::Start
})
.max_width(500),
) )
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .center_x(),
.center_x() ),
.center_y(), ))
)
.push(Space::with_height(Length::Fixed(100.0))),
)
.map(Message::View); .map(Message::View);
if let Some(modal) = &self.delete_wallet_modal { if let Some(modal) = &self.delete_wallet_modal {
Modal::new(content, modal.view()) Modal::new(Container::new(content).height(Length::Fill), modal.view())
.on_blur(Some(Message::View(ViewMessage::DeleteWallet( .on_blur(Some(Message::View(ViewMessage::DeleteWallet(
DeleteWalletMessage::CloseModal, DeleteWalletMessage::CloseModal,
)))) ))))
@ -229,14 +381,15 @@ impl Launcher {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Message { pub enum Message {
View(ViewMessage), View(ViewMessage),
Install(PathBuf), Install(PathBuf, Network),
Checked(Result<Network, String>), Checked(Result<Network, String>),
Run(PathBuf, app::config::Config, Network), Run(PathBuf, app::config::Config, Network),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ViewMessage { pub enum ViewMessage {
StartInstall, StartInstall(Network),
ShowUninstalledNetworks,
Check(Network), Check(Network),
DeleteWallet(DeleteWalletMessage), DeleteWallet(DeleteWalletMessage),
} }

View File

@ -198,13 +198,12 @@ impl Application for GUI {
} }
} }
(State::Launcher(l), Message::Launch(msg)) => match *msg { (State::Launcher(l), Message::Launch(msg)) => match *msg {
launcher::Message::Install(datadir_path) => { launcher::Message::Install(datadir_path, network) => {
self.logger.set_installer_mode( self.logger.set_installer_mode(
datadir_path.clone(), datadir_path.clone(),
self.log_level.unwrap_or(LevelFilter::INFO), self.log_level.unwrap_or(LevelFilter::INFO),
); );
let (install, command) = let (install, command) = Installer::new(datadir_path, network);
Installer::new(datadir_path, bitcoin::Network::Bitcoin);
self.state = State::Installer(Box::new(install)); self.state = State::Installer(Box::new(install));
command.map(|msg| Message::Install(Box::new(msg))) command.map(|msg| Message::Install(Box::new(msg)))
} }
@ -364,13 +363,6 @@ impl Config {
Err(ConfigError::NotFound) => Ok(Config::Install(datadir_path, network)), Err(ConfigError::NotFound) => Ok(Config::Install(datadir_path, network)),
Err(e) => Err(format!("Failed to read configuration file: {}", e).into()), Err(e) => Err(format!("Failed to read configuration file: {}", e).into()),
} }
} else if !datadir_path.exists()
|| (!datadir_path.join("bitcoin").exists()
&& !datadir_path.join("testnet").exists()
&& !datadir_path.join("signet").exists()
&& !datadir_path.join("regtest").exists())
{
Ok(Config::Install(datadir_path, bitcoin::Network::Bitcoin))
} else { } else {
Ok(Config::Launcher(datadir_path)) Ok(Config::Launcher(datadir_path))
} }