liana/liana-gui/src/installer/step/share_xpubs.rs
2024-11-15 13:26:13 +01:00

202 lines
6.1 KiB
Rust

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::editor::key::{default_derivation_path, get_extended_pubkey},
Context, Step,
},
view, Error,
},
signer::Signer,
};
pub struct HardwareWalletXpubs {
fingerprint: Fingerprint,
xpubs: Vec<String>,
processing: bool,
error: Option<Error>,
}
pub struct SignerXpubs {
signer: Arc<Mutex<Signer>>,
xpubs: Vec<String>,
next_account: ChildNumber,
words: [&'static str; 12],
did_backup: bool,
}
impl SignerXpubs {
fn new(signer: Arc<Mutex<Signer>>) -> Self {
let words = { signer.lock().unwrap().mnemonic() };
Self {
words,
signer,
xpubs: Vec::new(),
next_account: ChildNumber::from_hardened_idx(0).unwrap(),
did_backup: false,
}
}
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<Message> {
view::signer_xpubs(&self.xpubs, &self.words, self.did_backup)
}
}
pub struct ShareXpubs {
network: Network,
hw_xpubs: Vec<HardwareWalletXpubs>,
xpubs_signer: SignerXpubs,
}
impl ShareXpubs {
pub fn new(network: Network, signer: Arc<Mutex<Signer>>) -> Self {
Self {
network,
hw_xpubs: Vec::new(),
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<Message> {
match message {
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::UserActionDone(done) => {
self.xpubs_signer.did_backup = done;
}
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<Message> {
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),
email: Option<&'a str>,
) -> Element<Message> {
view::share_xpubs(
email,
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<ShareXpubs> for Box<dyn Step> {
fn from(s: ShareXpubs) -> Box<dyn Step> {
Box::new(s)
}
}