From 8efc55bcf5a314997ac4b2a1de9a0a587fb7c9fe Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Wed, 14 May 2025 17:27:18 +0200 Subject: [PATCH 1/2] ui: add a widget to let user select custom derivation path of an xpub --- liana-gui/src/installer/view/mod.rs | 24 +++-- liana-ui/src/component/hw.rs | 142 +++++++++++++++++++++++----- 2 files changed, 135 insertions(+), 31 deletions(-) diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs index 34e713668..76f707099 100644 --- a/liana-gui/src/installer/view/mod.rs +++ b/liana-gui/src/installer/view/mod.rs @@ -1684,13 +1684,21 @@ pub fn hw_list_view<'a>( } else if chosen && processing { hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } else if selected { - hw::selected_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref(), { - if not_tapminiscript { - Some("Device firmware version does not support taproot miniscript") - } else { - None - } - }) + hw::selected_hardware_wallet( + kind, + version.as_ref(), + fingerprint, + alias.as_ref(), + { + if not_tapminiscript { + Some("Device firmware version does not support taproot miniscript") + } else { + None + } + }, + None, + true, + ) } else if not_tapminiscript { hw::warning_hardware_wallet( kind, @@ -1758,6 +1766,8 @@ pub fn key_list_view<'a>( } else { None }, + None, + true, ) } else if device_must_support_taproot && kind.map(|kind| is_compatible_with_tapminiscript(kind, version)) == Some(false) diff --git a/liana-ui/src/component/hw.rs b/liana-ui/src/component/hw.rs index 964455ece..e9981b305 100644 --- a/liana-ui/src/component/hw.rs +++ b/liana-ui/src/component/hw.rs @@ -1,7 +1,8 @@ use crate::{color, component::text, icon, image, theme, widget::*}; +use bitcoin::bip32::{ChildNumber, Fingerprint}; use iced::{ alignment::Vertical, - widget::{column, container, row, tooltip, Space}, + widget::{column, container, pick_list, row, tooltip, Space}, Alignment, Length, }; use std::borrow::Cow; @@ -59,6 +60,88 @@ pub fn supported_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( .padding(10) } +#[derive(Debug, Clone, PartialEq)] +pub struct Account { + pub index: ChildNumber, + pub fingerprint: Fingerprint, +} + +impl Account { + pub fn new(index: ChildNumber, fingerprint: Fingerprint) -> Self { + Self { index, fingerprint } + } +} + +impl Display for Account { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let index = self.index.to_string(); + let index = index.replace("'", ""); + write!(f, "Account #{}", index) + } +} + +pub fn supported_hardware_wallet_with_account< + 'a, + M: 'static + From<(Fingerprint, ChildNumber)> + Clone, + K: Display, + V: Display, +>( + kind: K, + version: Option, + fingerprint: Fingerprint, + alias: Option> + Display>, + account: Option, + edit_account: bool, +) -> Container<'a, M> { + let accounts: Vec<_> = (0..10) + .map(|i| { + Account::new( + ChildNumber::from_hardened_idx(i).expect("hardcoded"), + fingerprint, + ) + }) + .collect(); + let account = Some(account.unwrap_or(ChildNumber::Hardened { index: 0 })); + let account = account.map(|i| Account::new(i, fingerprint)); + let pick_account = pick_list(accounts, account.clone(), |a| { + (a.fingerprint, a.index).into() + }); + let pick_account = if edit_account { + Some(pick_account) + } else { + None + }; + let display_account = account.and_then(|a| { + if !edit_account { + Some(text::p1_bold(a)) + } else { + None + } + }); + let key = column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text::p1_bold(a))) + .push(text::p1_regular(format!("#{}", fingerprint))) + .into(), + Row::new() + .spacing(5) + .push(text::caption(kind.to_string())) + .push_maybe(version.map(|v| text::caption(v.to_string()))) + .into(), + ]) + .width(Length::Fill); + Container::new( + Row::new() + .push(key) + .push(Space::with_width(Length::Fill)) + .push_maybe(pick_account) + .push_maybe(display_account.map(|a| column![Space::with_height(8), a])), + ) + .align_y(Alignment::Center) + .padding(10) +} + pub fn warning_hardware_wallet<'a, T: 'static, K: Display, V: Display, F: Display>( kind: K, version: Option, @@ -208,39 +291,50 @@ pub fn selected_hardware_wallet<'a, T: 'static, K: Display, V: Display, F: Displ fingerprint: F, alias: Option> + Display>, warning: Option<&'static str>, + account: Option, + display_account: bool, ) -> Container<'a, T> { - container( - row(vec![ - column(vec![ - Row::new() - .spacing(5) - .push_maybe(alias.map(text::p1_bold)) - .push(text::p1_regular(format!("#{}", fingerprint))) - .into(), - Row::new() - .spacing(5) - .push(text::caption(kind.to_string())) - .push_maybe(version.map(|v| text::caption(v))) - .into(), - ]) - .width(Length::Fill) + let account = account.unwrap_or(ChildNumber::from_hardened_idx(0).expect("hardcoded")); + let index = match account { + ChildNumber::Hardened { index } => index, + ChildNumber::Normal { .. } => unreachable!(), + }; + let account = if display_account { + Some(format!("Account #{index}")) + } else { + None + }; + + let key = column(vec![ + Row::new() + .spacing(5) + .push_maybe(alias.map(|a| text::p1_bold(a))) + .push(text::p1_regular(format!("#{}", fingerprint))) .into(), - if let Some(w) = warning { + Row::new() + .spacing(5) + .push(text::caption(kind.to_string())) + .push_maybe(version.map(|v| text::caption(v.to_string()))) + .into(), + ]); + container( + Row::new() + .push(key) + .push(Space::with_width(Length::Fill)) + .push_maybe(account.map(|a| column![Space::with_height(8), text::p1_bold(a)])) + .push(Space::with_width(10)) + .push_maybe(warning.map(|w| { tooltip::Tooltip::new( icon::warning_icon(), iced::widget::text!("{}", w), tooltip::Position::Bottom, ) .style(theme::card::simple) - .into() - } else { - Row::new().into() - }, - image::success_mark_icon().width(Length::Fixed(50.0)).into(), - ]) - .align_y(Alignment::Center), + })) + .push(image::success_mark_icon().width(Length::Fixed(50.0))), ) .padding(10) + .align_y(Alignment::Center) } pub fn sign_success_hardware_wallet<'a, T: 'a, K: Display, V: Display, F: Display>( From ce8e1f3c3eed5a6e991307f8740e54cb0680e5bc Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Thu, 15 May 2025 03:22:03 +0200 Subject: [PATCH 2/2] installer: let user choose account (derivation path) before fetching an xpub from signing device --- liana-gui/src/installer/descriptor.rs | 6 +- liana-gui/src/installer/message.rs | 12 ++- .../installer/step/descriptor/editor/key.rs | 79 +++++++++++++++++-- .../installer/step/descriptor/editor/mod.rs | 1 + liana-gui/src/installer/step/share_xpubs.rs | 21 ++++- liana-gui/src/installer/view/editor/mod.rs | 16 +++- liana-gui/src/installer/view/mod.rs | 68 ++++++++++++---- 7 files changed, 176 insertions(+), 27 deletions(-) diff --git a/liana-gui/src/installer/descriptor.rs b/liana-gui/src/installer/descriptor.rs index f39342e58..91ea699eb 100644 --- a/liana-gui/src/installer/descriptor.rs +++ b/liana-gui/src/installer/descriptor.rs @@ -1,5 +1,8 @@ use async_hwi::{DeviceKind, Version}; -use liana::miniscript::{bitcoin::bip32::Fingerprint, descriptor::DescriptorPublicKey}; +use liana::miniscript::{ + bitcoin::bip32::{ChildNumber, Fingerprint}, + descriptor::DescriptorPublicKey, +}; use crate::{ app::settings::ProviderKey, hw::is_compatible_with_tapminiscript, services::keys::api::KeyKind, @@ -107,6 +110,7 @@ pub struct Key { pub name: String, pub fingerprint: Fingerprint, pub key: DescriptorPublicKey, + pub account: Option, } pub struct Path { diff --git a/liana-gui/src/installer/message.rs b/liana-gui/src/installer/message.rs index f1a4cd7d7..04c618405 100644 --- a/liana-gui/src/installer/message.rs +++ b/liana-gui/src/installer/message.rs @@ -1,5 +1,8 @@ use liana::miniscript::{ - bitcoin::{bip32::Fingerprint, Network}, + bitcoin::{ + bip32::{ChildNumber, Fingerprint}, + Network, + }, DescriptorPublicKey, }; use std::collections::HashMap; @@ -67,6 +70,7 @@ pub enum Message { ImportBackup, WalletFromBackup((HashMap, Backup)), WalletAliasEdited(String), + SelectAccount(Fingerprint, ChildNumber), } impl Close for Message { @@ -81,6 +85,12 @@ impl From for Message { } } +impl From<(Fingerprint, ChildNumber)> for Message { + fn from(value: (Fingerprint, ChildNumber)) -> Self { + Self::SelectAccount(value.0, value.1) + } +} + #[derive(Debug, Clone)] pub enum SelectBackend { // view messages diff --git a/liana-gui/src/installer/step/descriptor/editor/key.rs b/liana-gui/src/installer/step/descriptor/editor/key.rs index 562213f8b..9b0bbc919 100644 --- a/liana-gui/src/installer/step/descriptor/editor/key.rs +++ b/liana-gui/src/installer/step/descriptor/editor/key.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::str::FromStr; use std::sync::{Arc, Mutex}; use iced::{Subscription, Task}; -use liana::miniscript::bitcoin::bip32::Xpub; +use liana::miniscript::bitcoin::bip32::{ChildNumber, Xpub}; use liana::miniscript::{ bitcoin::{ bip32::{DerivationPath, Fingerprint}, @@ -89,6 +89,7 @@ pub struct EditXpubModal { hot_signer_fingerprint: Fingerprint, chosen_signer: Option, modal: Option, + accounts: HashMap, } impl EditXpubModal { @@ -104,6 +105,10 @@ impl EditXpubModal { hot_signer_fingerprint: Fingerprint, keys: Vec, ) -> Self { + let accounts = keys + .iter() + .filter_map(|k| k.account.map(|acc| (k.fingerprint, acc))) + .collect(); Self { device_must_support_tapminiscript, path_kind, @@ -139,6 +144,7 @@ impl EditXpubModal { hot_signer, duplicate_master_fg: false, modal: None, + accounts, } } @@ -158,6 +164,10 @@ impl super::DescriptorEditModal for EditXpubModal { self.duplicate_master_fg = false; self.error = None; match message { + Message::SelectAccount(fg, index) => { + self.accounts.insert(fg, index); + return Task::none(); + } Message::Select(i) => { if let Some(HardwareWallet::Supported { device, @@ -170,6 +180,11 @@ impl super::DescriptorEditModal for EditXpubModal { self.processing = true; self.form_key_source_kind = None; let device_version = version.clone(); + let account = self + .accounts + .get(fingerprint) + .copied() + .unwrap_or(ChildNumber::from_hardened_idx(0).expect("hardcoded")); let fingerprint = *fingerprint; let device_kind = *kind; let device_cloned = device.clone(); @@ -181,10 +196,11 @@ impl super::DescriptorEditModal for EditXpubModal { device_kind, fingerprint, network, - get_extended_pubkey(device_cloned, fingerprint, network).await, + get_extended_pubkey(device_cloned, fingerprint, network, account) + .await, ) }, - |(device_version, device_kind, fingerprint, network, res)| { + move |(device_version, device_kind, fingerprint, network, res)| { Message::DefineDescriptor(message::DefineDescriptor::KeyModal( message::ImportKeyModal::FetchedKey(match res { Err(e) => Err(e), @@ -198,6 +214,7 @@ impl super::DescriptorEditModal for EditXpubModal { fingerprint, name: "".to_string(), key, + account: Some(account), }) } else { Err(Error::Unexpected( @@ -233,6 +250,7 @@ impl super::DescriptorEditModal for EditXpubModal { fingerprint, name: "".to_string(), key: DescriptorPublicKey::from_str(&key_str).unwrap(), + account: None, }); self.form_name.value = self .keys @@ -273,7 +291,7 @@ impl super::DescriptorEditModal for EditXpubModal { message::ImportKeyModal::FetchedKey(res) => { self.processing = false; match res { - Ok(key) => { + Ok(mut key) => { // If it is a provider key that has just been fetched, do some additional sanity checks. if let Some(key_kind) = key.source.provider_key_kind() { // We don't need to check key's status as redeemed keys are not returned. @@ -302,6 +320,7 @@ impl super::DescriptorEditModal for EditXpubModal { }; self.form_token.valid = self.form_token_warning.is_none(); } + key.account = self.accounts.get(&key.fingerprint).copied(); // User can set name for key if it is not a provider key or is a valid provider key. if key.source.provider_key().is_none() || self.form_token.valid { self.form_name.valid = key.name.is_empty() @@ -374,6 +393,7 @@ impl super::DescriptorEditModal for EditXpubModal { fingerprint, name: "".to_string(), key: DescriptorPublicKey::XPub(key), + account: None, }); self.form_name.value = "".to_string(); self.form_name.valid = true; @@ -429,6 +449,7 @@ impl super::DescriptorEditModal for EditXpubModal { key.kind ), key: key.xpub.clone(), + account: None, }), }), )) @@ -503,6 +524,8 @@ impl super::DescriptorEditModal for EditXpubModal { hw.fingerprint() == chosen_signer, None, self.device_must_support_tapminiscript, + Some(&self.accounts), + true, )) } }) @@ -528,6 +551,7 @@ impl super::DescriptorEditModal for EditXpubModal { key.source.device_version(), Some(key.fingerprint) == chosen_signer, self.device_must_support_tapminiscript, + &self.accounts, )) } }) @@ -576,14 +600,31 @@ pub fn default_derivation_path(network: Network) -> DerivationPath { .unwrap() } +pub fn derivation_path(network: Network, account: ChildNumber) -> DerivationPath { + assert!(account.is_hardened()); + let network = if network == Network::Bitcoin { + ChildNumber::Hardened { index: 0 } + } else { + ChildNumber::Hardened { index: 1 } + }; + vec![ + ChildNumber::Hardened { index: 48 }, + network, + account, + ChildNumber::Hardened { index: 2 }, + ] + .into() +} + /// LIANA_STANDARD_PATH: m/48'/0'/0'/2'; /// LIANA_TESTNET_STANDARD_PATH: m/48'/1'/0'/2'; pub async fn get_extended_pubkey( hw: std::sync::Arc, fingerprint: Fingerprint, network: Network, + account: ChildNumber, ) -> Result { - let derivation_path = default_derivation_path(network); + let derivation_path = derivation_path(network, account); let xkey = hw .get_extended_pubkey(&derivation_path) .await @@ -619,4 +660,30 @@ mod tests { "48'/1'/0'/2'" ); } + + #[test] + fn test_derivation_path() { + assert_eq!( + derivation_path(Network::Bitcoin, ChildNumber::Hardened { index: 0 }).to_string(), + "48'/0'/0'/2'" + ); + assert_eq!( + derivation_path(Network::Regtest, ChildNumber::Hardened { index: 0 }).to_string(), + "48'/1'/0'/2'" + ); + assert_eq!( + derivation_path(Network::Bitcoin, ChildNumber::Hardened { index: 1 }).to_string(), + "48'/0'/1'/2'" + ); + assert_eq!( + derivation_path(Network::Regtest, ChildNumber::Hardened { index: 1 }).to_string(), + "48'/1'/1'/2'" + ); + } + + #[test] + #[should_panic] + fn unhardened_derivation_path() { + derivation_path(Network::Bitcoin, ChildNumber::Normal { index: 0 }).to_string(); + } } diff --git a/liana-gui/src/installer/step/descriptor/editor/mod.rs b/liana-gui/src/installer/step/descriptor/editor/mod.rs index 45bc6b317..d9c33571f 100644 --- a/liana-gui/src/installer/step/descriptor/editor/mod.rs +++ b/liana-gui/src/installer/step/descriptor/editor/mod.rs @@ -764,6 +764,7 @@ mod tests { fingerprint: key.master_fingerprint(), key, source: KeySource::Device(async_hwi::DeviceKind::Specter, None), + account: None, }; // Use Specter device for primary key diff --git a/liana-gui/src/installer/step/share_xpubs.rs b/liana-gui/src/installer/step/share_xpubs.rs index 1befe5942..612eae172 100644 --- a/liana-gui/src/installer/step/share_xpubs.rs +++ b/liana-gui/src/installer/step/share_xpubs.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, Mutex}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; use iced::{Subscription, Task}; use liana::miniscript::bitcoin::{ @@ -73,6 +76,7 @@ pub struct ShareXpubs { hw_xpubs: Vec, xpubs_signer: SignerXpubs, modal: Option, + accounts: HashMap, } impl ShareXpubs { @@ -82,6 +86,7 @@ impl ShareXpubs { hw_xpubs: Vec::new(), xpubs_signer: SignerXpubs::new(signer), modal: None, + accounts: Default::default(), } } } @@ -91,6 +96,10 @@ impl Step for ShareXpubs { // Verification of the values is happening when the user click on Next button. fn update(&mut self, hws: &mut HardwareWallets, message: Message) -> Task { match message { + Message::SelectAccount(fg, index) => { + self.accounts.insert(fg, index); + return Task::none(); + } Message::ImportXpub(fg, res) => { if let Some(hw_xpubs) = self.hw_xpubs.iter_mut().find(|x| x.fingerprint == fg) { hw_xpubs.processing = false; @@ -138,6 +147,11 @@ impl Step for ShareXpubs { }) = hws.list.get(i) { let device = device.clone(); + let account = self + .accounts + .get(fingerprint) + .copied() + .unwrap_or(ChildNumber::from_hardened_idx(0).expect("hardcoded")); let fingerprint = *fingerprint; let network = self.network; if let Some(hw_xpubs) = self @@ -159,7 +173,7 @@ impl Step for ShareXpubs { async move { ( fingerprint, - get_extended_pubkey(device, fingerprint, network).await, + get_extended_pubkey(device, fingerprint, network, account).await, ) }, |(fingerprint, res)| Message::ImportXpub(fingerprint, res), @@ -212,9 +226,10 @@ impl Step for ShareXpubs { Some(&hw_xpubs.xpubs), hw_xpubs.processing, hw_xpubs.error.as_ref(), + &self.accounts, ) } else { - view::hardware_wallet_xpubs(i, hw, None, false, None) + view::hardware_wallet_xpubs(i, hw, None, false, None, &self.accounts) } }) .collect(), diff --git a/liana-gui/src/installer/view/editor/mod.rs b/liana-gui/src/installer/view/editor/mod.rs index 6a15deb57..1886b06b6 100644 --- a/liana-gui/src/installer/view/editor/mod.rs +++ b/liana-gui/src/installer/view/editor/mod.rs @@ -2,7 +2,7 @@ pub mod template; -use iced::widget::{container, pick_list, scrollable, slider, Button, Space}; +use iced::widget::{self, container, pick_list, scrollable, slider, Button, Space}; use iced::{Alignment, Length}; use liana::miniscript::bitcoin::Network; @@ -353,6 +353,18 @@ pub fn edit_key_modal<'a>( duplicate_master_fg: bool, ) -> Element<'a, Message> { let xpub_valid = form_xpub.valid && !form_xpub.value.is_empty(); + let info = Column::new() + .push(Space::with_height(5)) + .push(widget::tooltip::Tooltip::new( + icon::tooltip_icon(), + "Switch account if you already use the same hardware in other configurations", + widget::tooltip::Position::Bottom, + )); + let source = Row::new() + .push(p1_regular("Select the source of your key").bold()) + .push(Space::with_width(10)) + .push(info) + .push(Space::with_width(Length::Fill)); let content = Column::new() .padding(25) .push_maybe(error.map(|e| card::error("Failed to import xpub", e.to_string()))) @@ -367,7 +379,7 @@ pub fn edit_key_modal<'a>( ) .push( Column::new() - .push(p1_regular("Select the source of your key")) + .push(source) .spacing(10) .push(Column::with_children(hws).spacing(10)) .push(Column::with_children(keys).spacing(10)) diff --git a/liana-gui/src/installer/view/mod.rs b/liana-gui/src/installer/view/mod.rs index 76f707099..223881025 100644 --- a/liana-gui/src/installer/view/mod.rs +++ b/liana-gui/src/installer/view/mod.rs @@ -1,7 +1,9 @@ pub mod editor; use async_hwi::utils::extract_keys_and_template; -use iced::widget::{checkbox, radio, scrollable, scrollable::Scrollbar, Button, Space, TextInput}; +use iced::widget::{ + checkbox, radio, scrollable, scrollable::Scrollbar, tooltip, Button, Space, TextInput, +}; use iced::{ alignment, widget::{progress_bar, tooltip as iced_tooltip}, @@ -9,6 +11,7 @@ use iced::{ }; use async_hwi::DeviceKind; +use liana::miniscript::bitcoin::bip32::ChildNumber; use liana_ui::component::text::{self, p2_regular}; use std::collections::HashMap; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -473,6 +476,7 @@ pub fn hardware_wallet_xpubs<'a>( xpubs: Option<&'a Vec>, processing: bool, error: Option<&Error>, + accounts: &HashMap, ) -> Element<'a, Message> { let mut bttn = Button::new(match hw { HardwareWallet::Supported { @@ -485,7 +489,14 @@ pub fn hardware_wallet_xpubs<'a>( if processing { hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } else { - hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + hw::supported_hardware_wallet_with_account( + kind, + version.as_ref(), + *fingerprint, + alias.as_ref(), + accounts.get(fingerprint).cloned(), + true, + ) } } HardwareWallet::Unsupported { @@ -563,17 +574,24 @@ pub fn share_xpubs<'a>( hws: Vec>, signer: Element<'a, Message>, ) -> Element<'a, Message> { + let info = Column::new() + .push(Space::with_height(5)) + .push(tooltip::Tooltip::new( + icon::tooltip_icon(), + "Switch account if you already use the same hardware in other configurations", + tooltip::Position::Bottom, + )); + let title = Row::new() + .push(text("Import an extended public key by selecting a signing device:").bold()) + .push(Space::with_width(10)) + .push(info) + .push(Space::with_width(Length::Fill)); layout( (0, 0), email, "Share your public keys (Xpubs)", Column::new() - .push( - Container::new( - text("Import an extended public key by selecting a signing device:").bold(), - ) - .width(Length::Fill), - ) + .push(title) .push_maybe(if hws.is_empty() { Some(p1_regular("No signing device connected").style(theme::text::secondary)) } else { @@ -718,6 +736,8 @@ pub fn register_descriptor<'a>( .unwrap_or(false), Some(descriptor), false, + None, + false, )) }), ) @@ -1655,6 +1675,7 @@ pub fn defined_sequence<'a>( .into() } +#[allow(clippy::too_many_arguments)] pub fn hw_list_view<'a>( i: usize, hw: &'a HardwareWallet, @@ -1663,6 +1684,8 @@ pub fn hw_list_view<'a>( selected: bool, descriptor: Option<&'a LianaDescriptor>, device_must_support_taproot: bool, + accounts: Option<&HashMap>, + display_account: bool, ) -> Element<'a, Message> { let mut unrelated = false; let mut bttn = Button::new(match hw { @@ -1684,6 +1707,9 @@ pub fn hw_list_view<'a>( } else if chosen && processing { hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) } else if selected { + let acc = accounts + .as_ref() + .and_then(|map| map.get(fingerprint).cloned()); hw::selected_hardware_wallet( kind, version.as_ref(), @@ -1696,8 +1722,8 @@ pub fn hw_list_view<'a>( None } }, - None, - true, + acc, + display_account, ) } else if not_tapminiscript { hw::warning_hardware_wallet( @@ -1707,8 +1733,17 @@ pub fn hw_list_view<'a>( alias.as_ref(), "Device firmware version does not support taproot miniscript", ) + } else if let Some(accounts) = accounts { + hw::supported_hardware_wallet_with_account( + kind, + version.as_ref(), + *fingerprint, + alias.as_ref(), + accounts.get(fingerprint).cloned(), + true, + ) } else { - hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref()) + hw::supported_hardware_wallet(kind, version.as_ref(), *fingerprint, alias.as_ref()) } } HardwareWallet::Unsupported { @@ -1744,6 +1779,7 @@ pub fn hw_list_view<'a>( bttn.into() } +#[allow(clippy::too_many_arguments)] pub fn key_list_view<'a>( i: usize, name: &'a str, @@ -1752,7 +1788,9 @@ pub fn key_list_view<'a>( version: Option<&'a async_hwi::Version>, chosen: bool, device_must_support_taproot: bool, + accounts: &HashMap, ) -> Element<'a, Message> { + let account = accounts.get(fingerprint).copied(); Button::new(if chosen { hw::selected_hardware_wallet( kind.map(|k| k.to_string()).unwrap_or_default(), @@ -1766,7 +1804,7 @@ pub fn key_list_view<'a>( } else { None }, - None, + account, true, ) } else if device_must_support_taproot @@ -1780,11 +1818,13 @@ pub fn key_list_view<'a>( "Device firmware version does not support taproot miniscript", ) } else { - hw::supported_hardware_wallet( + hw::supported_hardware_wallet_with_account( kind.map(|k| k.to_string()).unwrap_or_default(), version, - fingerprint, + *fingerprint, Some(name), + account, + false, ) }) .style(theme::button::secondary)