Merge #999: Add jade

570f0af35b84372d0e4684f9afd42ea7c900b43e installer: refresh hw list only if modal is open (edouardparis)
4e2015922d2a40ce934440a23f18fe8d1323ebcc doc: add Jade section to signing devices (edouardparis)
6b0c93c5c3fe2a5b6e7a3dfe49755c6edc704510 Add jade hardware wallet (edouardparis)

Pull request description:

ACKs for top commit:
  edouardparis:
    Self-ACK 570f0af35b84372d0e4684f9afd42ea7c900b43e

Tree-SHA512: 3f27c8bcc7afa74f751de245b44f53ce3e99b42175809566e2bf15832600d6466afd56684de08333a11079b7cb1daca3412e7224299d98f0c76cf2575b48494d
This commit is contained in:
edouardparis 2024-05-06 18:11:31 +02:00
commit b22e9e8f71
No known key found for this signature in database
GPG Key ID: E65F7A089C20DC8F
11 changed files with 391 additions and 122 deletions

View File

@ -33,3 +33,18 @@ As of this writing, Coldcard on Taproot will only be usable for descriptors whic
key as their primary path. This is due to a discrepancy in how Coldcard derives [deterministically
unspendable Taproot internal
keys](https://delvingbitcoin.org/t/unspendable-keys-in-descriptors/304).
## [Jade](https://github.com/Blockstream/Jade)
Version 1.0.30 of the firmware is supported for use in P2WSH descriptors.
Support for use in Taproot descriptors is not yet available in the firmware.
After the setup of the device, the first connection to set the pin will set also the network.
The network cannot be change unless doing a factory reset.
If using "QrCode" mode, the device will refuse other communication channels like USB.
If using "Temporary Signer", the first connection through USB will setup the network, a new session
is required in order to change it. If using the Liana gui installer, is is advised to first choose
the network before connecting the Jade in "Temporary signer" mode.

124
gui/Cargo.lock generated
View File

@ -2,27 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "CoreFoundation-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
dependencies = [
"libc",
"mach 0.1.2",
]
[[package]]
name = "IOKit-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
dependencies = [
"CoreFoundation-sys",
"libc",
"mach 0.1.2",
]
[[package]]
name = "ab_glyph"
version = "0.2.20"
@ -214,9 +193,9 @@ dependencies = [
[[package]]
name = "async-hwi"
version = "0.0.16"
version = "0.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "912663643d018301fb5534949e8430515c7cdc9bf453bfefba2dc70dc854dd31"
checksum = "647a2513ce55652719759c6416e98de94b19a349dc94e1be4924b0487f73ce9b"
dependencies = [
"async-trait",
"bitbox-api",
@ -227,10 +206,17 @@ dependencies = [
"ledger-apdu",
"ledger-transport-hidapi",
"ledger_bitcoin_client",
"prost 0.12.2",
"prost-derive 0.12.2",
"regex",
"reqwest",
"serde",
"serde_bytes",
"serde_cbor",
"serialport",
"tokio",
"tokio-serial",
"zeroize",
]
[[package]]
@ -1353,7 +1339,7 @@ checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4"
dependencies = [
"bit_field",
"flume",
"half",
"half 2.2.1",
"lebe",
"miniz_oxide",
"rayon-core",
@ -1853,6 +1839,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "half"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
[[package]]
name = "half"
version = "2.2.1"
@ -2132,7 +2124,7 @@ dependencies = [
"bitflags 2.4.2",
"bytemuck",
"cosmic-text",
"half",
"half 2.2.1",
"iced_core",
"iced_futures",
"image",
@ -2337,6 +2329,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "io-kit-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
dependencies = [
"core-foundation-sys",
"mach2",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
@ -2810,19 +2812,10 @@ dependencies = [
]
[[package]]
name = "mach"
version = "0.1.2"
name = "mach2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
dependencies = [
"libc",
]
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
dependencies = [
"libc",
]
@ -2957,7 +2950,7 @@ checksum = "20a4c60ca5c9c0e114b3bd66ff4aa5f9b2b175442be51ca6c4365d687a97a2ac"
dependencies = [
"log",
"mio",
"nix 0.26.2",
"nix",
"serialport",
"winapi",
]
@ -3033,17 +3026,6 @@ dependencies = [
"jni-sys",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]]
name = "nix"
version = "0.26.2"
@ -4253,6 +4235,25 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde_bytes"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
dependencies = [
"serde",
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half 1.8.3",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.186"
@ -4289,18 +4290,20 @@ dependencies = [
[[package]]
name = "serialport"
version = "4.2.0"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aab92efb5cf60ad310548bc3f16fa6b0d950019cb7ed8ff41968c3d03721cf12"
checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828"
dependencies = [
"CoreFoundation-sys",
"IOKit-sys",
"bitflags 1.3.2",
"bitflags 2.4.2",
"cfg-if",
"core-foundation-sys",
"io-kit-sys",
"libudev",
"mach 0.3.2",
"nix 0.24.3",
"mach2",
"nix",
"regex",
"scopeguard",
"unescaper",
"winapi",
]
@ -4942,6 +4945,15 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unescaper"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0adf6ad32eb5b3cadff915f7b770faaac8f7ff0476633aa29eb0d9584d889d34"
dependencies = [
"thiserror",
]
[[package]]
name = "unicode-bidi"
version = "0.3.13"

View File

@ -15,7 +15,7 @@ path = "src/main.rs"
[dependencies]
async-trait = "0.1"
async-hwi = "0.0.16"
async-hwi = "0.0.17"
liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] }
liana_ui = { path = "ui" }
backtrace = "0.3"

View File

@ -53,6 +53,9 @@ pub fn hw_list_view(
UnsupportedReason::NotPartOfWallet(fg) => {
hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg)
}
UnsupportedReason::WrongNetwork => {
hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref())
}
_ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()),
},
HardwareWallet::Locked {
@ -111,6 +114,9 @@ pub fn hw_list_view_for_registration(
UnsupportedReason::NotPartOfWallet(fg) => {
hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg)
}
UnsupportedReason::WrongNetwork => {
hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref())
}
_ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()),
},
HardwareWallet::Locked {
@ -180,6 +186,9 @@ pub fn hw_list_view_verify_address(
UnsupportedReason::NotPartOfWallet(fg) => {
hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg)
}
UnsupportedReason::WrongNetwork => {
hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref())
}
_ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()),
},
false,

View File

@ -8,7 +8,9 @@ use std::{
use crate::app::{settings, wallet::Wallet};
use async_hwi::{
bitbox::{api::runtime, BitBox02, PairingBitbox02},
coldcard, ledger, specter, DeviceKind, Error as HWIError, Version, HWI,
coldcard,
jade::{self, Jade},
ledger, specter, DeviceKind, Error as HWIError, Version, HWI,
};
use liana::miniscript::bitcoin::{bip32::Fingerprint, hashes::hex::FromHex, Network};
use serde::{Deserialize, Serialize};
@ -21,6 +23,7 @@ pub enum UnsupportedReason {
},
Method(&'static str),
NotPartOfWallet(Fingerprint),
WrongNetwork,
}
// Todo drop the Clone, to remove the Mutex on HardwareWallet::Locked
@ -51,7 +54,8 @@ pub enum HardwareWallet {
}
pub enum LockedDevice {
BitBox02(PairingBitbox02<runtime::TokioRuntime>),
BitBox02(Box<PairingBitbox02<runtime::TokioRuntime>>),
Jade(Jade<jade::SerialTransport>),
}
impl std::fmt::Debug for LockedDevice {
@ -200,6 +204,7 @@ impl HardwareWallets {
pub fn set_network(&mut self, network: Network) {
self.network = network;
self.list = Vec::new();
}
pub fn update(
@ -221,25 +226,47 @@ impl HardwareWallets {
*alias = self.aliases.get(fingerprint).cloned();
}
HardwareWallet::Locked { device, id, .. } => {
if let Some(LockedDevice::BitBox02(bb)) = device.lock().unwrap().take()
{
let id = id.to_string();
let id_cloned = id.clone();
let network = self.network;
let wallet = self.wallet.clone();
cmds.push(Command::perform(
async move {
let paired_bb = bb.wait_confirm().await?;
let mut bitbox2 =
BitBox02::from(paired_bb).with_network(network);
let fingerprint = bitbox2.get_master_fingerprint().await?;
let mut registered = false;
if let Some(wallet) = &wallet {
let desc = wallet.main_descriptor.to_string();
bitbox2 = bitbox2.with_policy(&desc)?;
registered =
bitbox2.is_policy_registered(&desc).await?;
if wallet.descriptor_keys().contains(&fingerprint) {
match device.lock().unwrap().take() {
None => {}
Some(LockedDevice::BitBox02(bb)) => {
let id = id.to_string();
let id_cloned = id.clone();
let network = self.network;
let wallet = self.wallet.clone();
cmds.push(Command::perform(
async move {
let paired_bb = bb.wait_confirm().await?;
let mut bitbox2 =
BitBox02::from(paired_bb).with_network(network);
let fingerprint =
bitbox2.get_master_fingerprint().await?;
let mut registered = false;
if let Some(wallet) = &wallet {
let desc = wallet.main_descriptor.to_string();
bitbox2 = bitbox2.with_policy(&desc)?;
registered =
bitbox2.is_policy_registered(&desc).await?;
if wallet.descriptor_keys().contains(&fingerprint) {
Ok(HardwareWallet::Supported {
id: id.clone(),
kind: DeviceKind::BitBox02,
fingerprint,
device: bitbox2.into(),
version: None,
registered: Some(registered),
alias: None,
})
} else {
Ok(HardwareWallet::Unsupported {
id: id.clone(),
kind: DeviceKind::BitBox02,
version: None,
reason: UnsupportedReason::NotPartOfWallet(
fingerprint,
),
})
}
} else {
Ok(HardwareWallet::Supported {
id: id.clone(),
kind: DeviceKind::BitBox02,
@ -249,30 +276,31 @@ impl HardwareWallets {
registered: Some(registered),
alias: None,
})
} else {
Ok(HardwareWallet::Unsupported {
id: id.clone(),
kind: DeviceKind::BitBox02,
version: None,
reason: UnsupportedReason::NotPartOfWallet(
fingerprint,
),
})
}
} else {
Ok(HardwareWallet::Supported {
id: id.clone(),
kind: DeviceKind::BitBox02,
fingerprint,
device: bitbox2.into(),
version: None,
registered: Some(registered),
alias: None,
})
}
},
|res| HardwareWalletMessage::Unlocked(id_cloned, res),
));
},
|res| HardwareWalletMessage::Unlocked(id_cloned, res),
));
}
Some(LockedDevice::Jade(device)) => {
let id = id.clone();
let id_cloned = id.clone();
let network = self.network;
let wallet = self.wallet.clone();
cmds.push(Command::perform(
async move {
device.auth().await?;
handle_jade_device(
id,
network,
device,
wallet.as_ref().map(|w| w.as_ref()),
None,
)
.await
},
|res| HardwareWalletMessage::Unlocked(id_cloned, res),
));
}
}
}
_ => {}
@ -286,8 +314,8 @@ impl HardwareWallets {
}
HardwareWalletMessage::Unlocked(id, res) => {
match res {
Err(_) => {
warn!("Pairing failed with an external device");
Err(e) => {
warn!("Pairing failed with an external device {}", e);
self.list.retain(|hw| hw.id() != &id);
}
Ok(hw) => {
@ -317,6 +345,7 @@ impl HardwareWallets {
iced::subscription::unfold(
format!("refresh-{}", self.network),
State {
network: self.network,
keys_aliases: self.aliases.clone(),
wallet: self.wallet.clone(),
connected_supported_hws: Vec::new(),
@ -329,6 +358,7 @@ impl HardwareWallets {
}
struct State {
network: Network,
keys_aliases: HashMap<Fingerprint, String>,
wallet: Option<Arc<Wallet>>,
connected_supported_hws: Vec<String>,
@ -406,6 +436,38 @@ async fn refresh(mut state: State) -> (HardwareWalletMessage, State) {
}
Err(e) => warn!("Error while listing specter wallets: {}", e),
}
match jade::SerialTransport::enumerate_potential_ports() {
Ok(ports) => {
for port in ports {
let id = format!("jade-{}", port);
if state.connected_supported_hws.contains(&id) {
still.push(id);
} else {
let device =
Jade::new(jade::SerialTransport::new(port)).with_network(state.network);
match handle_jade_device(
id,
state.network,
device,
state.wallet.as_ref().map(|w| w.as_ref()),
Some(&state.keys_aliases),
)
.await
{
Ok(hw) => {
hws.push(hw);
}
Err(e) => {
warn!("{:?}", e);
}
}
}
}
}
Err(e) => warn!("Error while listing jade devices: {}", e),
}
match ledger::LedgerSimulator::try_connect().await {
Ok(mut device) => {
let id = "ledger-simulator".to_string();
@ -497,7 +559,9 @@ async fn refresh(mut state: State) -> (HardwareWalletMessage, State) {
id,
kind: DeviceKind::BitBox02,
pairing_code: device.pairing_code().map(|s| s.replace('\n', " ")),
device: Arc::new(Mutex::new(Some(LockedDevice::BitBox02(device)))),
device: Arc::new(Mutex::new(Some(LockedDevice::BitBox02(Box::new(
device,
))))),
});
}
}
@ -648,6 +712,94 @@ async fn refresh(mut state: State) -> (HardwareWalletMessage, State) {
)
}
async fn handle_jade_device(
id: String,
network: Network,
device: Jade<async_hwi::jade::SerialTransport>,
wallet: Option<&Wallet>,
keys_aliases: Option<&HashMap<Fingerprint, String>>,
) -> Result<HardwareWallet, HWIError> {
let info = device.get_info().await?;
let version = async_hwi::parse_version(&info.jade_version).ok();
// Jade may not be setup for the current network
if (network == Network::Bitcoin
&& info.jade_networks != jade::api::JadeNetworks::Main
&& info.jade_networks != jade::api::JadeNetworks::All)
|| (network != Network::Bitcoin && info.jade_networks == jade::api::JadeNetworks::Main)
{
Ok(HardwareWallet::Unsupported {
id,
kind: device.device_kind(),
version,
reason: UnsupportedReason::WrongNetwork,
})
} else {
match info.jade_state {
jade::api::JadeState::Locked
| jade::api::JadeState::Temp
| jade::api::JadeState::Uninit
| jade::api::JadeState::Unsaved => Ok(HardwareWallet::Locked {
id,
kind: DeviceKind::Jade,
pairing_code: None,
device: Arc::new(Mutex::new(Some(LockedDevice::Jade(device)))),
}),
jade::api::JadeState::Ready => {
let kind = device.device_kind();
let version = device.get_version().await.ok();
let fingerprint = match device.get_master_fingerprint().await {
Err(HWIError::NetworkMismatch) => {
return Ok(HardwareWallet::Unsupported {
id: id.clone(),
kind,
version,
reason: UnsupportedReason::WrongNetwork,
});
}
Err(e) => {
return Err(e);
}
Ok(fingerprint) => fingerprint,
};
let alias = keys_aliases.and_then(|aliases| aliases.get(&fingerprint).cloned());
if let Some(wallet) = &wallet {
if wallet.descriptor_keys().contains(&fingerprint) {
let desc = wallet.main_descriptor.to_string();
let device = device.with_wallet(wallet.name.clone());
let registered = device.is_wallet_registered(&wallet.name, &desc).await?;
Ok(HardwareWallet::Supported {
id: id.clone(),
kind,
fingerprint,
device: Arc::new(device),
version,
registered: Some(registered),
alias,
})
} else {
Ok(HardwareWallet::Unsupported {
id: id.clone(),
kind,
version,
reason: UnsupportedReason::NotPartOfWallet(fingerprint),
})
}
} else {
Ok(HardwareWallet::Supported {
id: id.clone(),
kind,
fingerprint,
device: Arc::new(device),
version,
registered: Some(false),
alias,
})
}
}
}
}
}
struct AsRefWrap<'a, T> {
inner: &'a T,
}

View File

@ -73,13 +73,10 @@ impl Installer {
pub fn subscription(&self) -> Subscription<Message> {
if self.current > 0 {
Subscription::batch(vec![
self.hws.refresh().map(Message::HardwareWallets),
self.steps
.get(self.current)
.expect("There is always a step")
.subscription(),
])
self.steps
.get(self.current)
.expect("There is always a step")
.subscription(&self.hws)
} else {
Subscription::none()
}

View File

@ -739,7 +739,7 @@ impl Step for InternalBitcoindStep {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
fn subscription(&self, _hws: &HardwareWallets) -> Subscription<Message> {
if let Some(download) = self.exe_download.as_ref() {
return download.subscription();
}

View File

@ -4,7 +4,7 @@ use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use iced::Command;
use iced::{Command, Subscription};
use liana::miniscript::bitcoin::bip32::Xpub;
use liana::{
descriptors::{LianaDescriptor, LianaPolicy, PathInfo},
@ -46,6 +46,9 @@ pub trait DescriptorEditModal {
Command::none()
}
fn view<'a>(&'a self, _hws: &'a HardwareWallets) -> Element<'a, Message>;
fn subscription(&self, _hws: &HardwareWallets) -> Subscription<Message> {
Subscription::none()
}
}
pub struct RecoveryPath {
@ -461,6 +464,14 @@ impl Step for DefineDescriptor {
self.set_network(ctx.bitcoin_config.network)
}
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
if let Some(modal) = &self.modal {
modal.subscription(hws)
} else {
Subscription::none()
}
}
fn apply(&mut self, ctx: &mut Context) -> bool {
ctx.bitcoin_config.network = self.network;
ctx.keys = Vec::new();
@ -1013,6 +1024,11 @@ impl DescriptorEditModal for EditXpubModal {
};
Command::none()
}
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
hws.refresh().map(Message::HardwareWallets)
}
fn view<'a>(&'a self, hws: &'a HardwareWallets) -> Element<'a, Message> {
let chosen_signer = self.chosen_signer.as_ref().map(|s| s.0);
view::edit_key_modal(
@ -1272,6 +1288,10 @@ impl Step for ParticipateXpub {
Command::none()
}
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
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);
@ -1550,6 +1570,9 @@ impl Step for RegisterDescriptor {
}
true
}
fn subscription(&self, hws: &HardwareWallets) -> Subscription<Message> {
hws.refresh().map(Message::HardwareWallets)
}
fn load(&self) -> Command<Message> {
Command::none()
}

View File

@ -28,7 +28,7 @@ pub trait Step {
fn update(&mut self, _hws: &mut HardwareWallets, _message: Message) -> Command<Message> {
Command::none()
}
fn subscription(&self) -> Subscription<Message> {
fn subscription(&self, _hws: &HardwareWallets) -> Subscription<Message> {
Subscription::none()
}
fn view<'a>(

View File

@ -23,7 +23,7 @@ use liana_ui::{
use crate::{
bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError},
hw::{is_compatible_with_tapminiscript, HardwareWallet},
hw::{is_compatible_with_tapminiscript, HardwareWallet, UnsupportedReason},
installer::{
message::{self, Message},
prompt,
@ -614,9 +614,20 @@ pub fn hardware_wallet_xpubs<'a>(
hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())
}
}
HardwareWallet::Unsupported { version, kind, .. } => {
hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref())
}
HardwareWallet::Unsupported {
version,
kind,
reason,
..
} => match reason {
UnsupportedReason::NotPartOfWallet(fg) => {
hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg)
}
UnsupportedReason::WrongNetwork => {
hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref())
}
_ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()),
},
HardwareWallet::Locked {
kind, pairing_code, ..
} => hw::locked_hardware_wallet(kind, pairing_code.as_ref()),
@ -1745,9 +1756,20 @@ pub fn hw_list_view(
hw::supported_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())
}
}
HardwareWallet::Unsupported { version, kind, .. } => {
hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref())
}
HardwareWallet::Unsupported {
version,
kind,
reason,
..
} => match reason {
UnsupportedReason::NotPartOfWallet(fg) => {
hw::unrelated_hardware_wallet(&kind.to_string(), version.as_ref(), fg)
}
UnsupportedReason::WrongNetwork => {
hw::wrong_network_hardware_wallet(&kind.to_string(), version.as_ref())
}
_ => hw::unsupported_hardware_wallet(&kind.to_string(), version.as_ref()),
},
HardwareWallet::Locked {
kind, pairing_code, ..
} => hw::locked_hardware_wallet(kind, pairing_code.as_ref()),

View File

@ -14,7 +14,14 @@ pub fn locked_hardware_wallet<'a, T: 'a, K: Display>(
column(vec![
Row::new()
.spacing(5)
.push(text::p1_bold("Locked, check code:"))
.push(text::p1_bold(format!(
"Locked{}",
if pairing_code.is_some() {
", check code:"
} else {
""
}
)))
.push_maybe(pairing_code.map(|a| text::p1_bold(a)))
.into(),
Row::new()
@ -277,6 +284,38 @@ pub fn registration_success_hardware_wallet<'a, T: 'a, K: Display, V: Display, F
.padding(10)
}
pub fn wrong_network_hardware_wallet<'a, T: 'a, K: Display, V: Display>(
kind: K,
version: Option<V>,
) -> Container<'a, T> {
container(
row(vec![
column(vec![
Row::new()
.spacing(5)
.push(text::p1_bold("Wrong network in the device settings"))
.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)
.into(),
tooltip::Tooltip::new(
icon::warning_icon(),
"The wrong bitcoin application is open or the device was initialized with the wrong network",
tooltip::Position::Bottom,
)
.style(theme::Container::Card(theme::Card::Simple))
.into(),
])
.align_items(Alignment::Center),
)
.padding(10)
}
pub fn unsupported_hardware_wallet<'a, T: 'a, K: Display, V: Display>(
kind: K,
version: Option<V>,