diff --git a/clap-utils/src/keypair.rs b/clap-utils/src/keypair.rs index 2b625ca9a..1de568d32 100644 --- a/clap-utils/src/keypair.rs +++ b/clap-utils/src/keypair.rs @@ -87,6 +87,7 @@ pub fn signer_from_path( derivation_of(matches, "derivation_path"), wallet_manager, matches.is_present("confirm_key"), + keypair_name, )?)) } else { Err(RemoteWalletError::NoDeviceFound.into()) diff --git a/remote-wallet/src/ledger.rs b/remote-wallet/src/ledger.rs index a0d0962eb..ced0defa6 100644 --- a/remote-wallet/src/ledger.rs +++ b/remote-wallet/src/ledger.rs @@ -190,7 +190,9 @@ impl LedgerWallet { #[allow(clippy::match_overlapping_arm)] match status { // These need to be aligned with solana Ledger app error codes, and clippy allowance removed - 0x6700 => Err(RemoteWalletError::Protocol("Incorrect length")), + 0x6700 => Err(RemoteWalletError::Protocol( + "Solana app not open on Ledger device", + )), 0x6802 => Err(RemoteWalletError::Protocol("Invalid parameter")), 0x6803 => Err(RemoteWalletError::Protocol( "Overflow: message longer than MAX_MESSAGE_LENGTH", @@ -265,13 +267,18 @@ impl RemoteWallet for LedgerWallet { .serial_number .clone() .unwrap_or_else(|| "Unknown".to_owned()); - self.get_pubkey(&DerivationPath::default(), false) - .map(|pubkey| RemoteWalletInfo { - model, - manufacturer, - serial, - pubkey, - }) + let pubkey_result = self.get_pubkey(&DerivationPath::default(), false); + let (pubkey, error) = match pubkey_result { + Ok(pubkey) => (pubkey, None), + Err(err) => (Pubkey::default(), Some(err)), + }; + Ok(RemoteWalletInfo { + model, + manufacturer, + serial, + pubkey, + error, + }) } fn get_pubkey( @@ -395,12 +402,24 @@ fn extend_and_serialize(derivation_path: &DerivationPath) -> Vec { /// Choose a Ledger wallet based on matching info fields pub fn get_ledger_from_info( info: RemoteWalletInfo, + keypair_name: &str, wallet_manager: &RemoteWalletManager, ) -> Result, RemoteWalletError> { let devices = wallet_manager.list_devices(); - let (pubkeys, device_paths): (Vec, Vec) = devices + let mut matches = devices .iter() - .filter(|&device_info| device_info.matches(&info)) + .filter(|&device_info| device_info.matches(&info)); + if matches + .clone() + .all(|device_info| device_info.error.is_some()) + { + let first_device = matches.next(); + if let Some(device) = first_device { + return Err(device.error.clone().unwrap()); + } + } + let (pubkeys, device_paths): (Vec, Vec) = matches + .filter(|&device_info| device_info.error.is_none()) .map(|device_info| (device_info.pubkey, device_info.get_pretty_path())) .unzip(); if pubkeys.is_empty() { @@ -408,7 +427,10 @@ pub fn get_ledger_from_info( } let wallet_base_pubkey = if pubkeys.len() > 1 { let selection = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Multiple hardware wallets found. Please select a device") + .with_prompt(&format!( + "Multiple hardware wallets found. Please select a device for {:?}", + keypair_name + )) .default(0) .items(&device_paths[..]) .interact() diff --git a/remote-wallet/src/remote_keypair.rs b/remote-wallet/src/remote_keypair.rs index 7cd656f3b..383dfbfff 100644 --- a/remote-wallet/src/remote_keypair.rs +++ b/remote-wallet/src/remote_keypair.rs @@ -53,13 +53,14 @@ pub fn generate_remote_keypair( explicit_derivation_path: Option, wallet_manager: &RemoteWalletManager, confirm_key: bool, + keypair_name: &str, ) -> Result { let (remote_wallet_info, mut derivation_path) = RemoteWalletInfo::parse_path(path)?; if let Some(derivation) = explicit_derivation_path { derivation_path = derivation; } if remote_wallet_info.manufacturer == "ledger" { - let ledger = get_ledger_from_info(remote_wallet_info, wallet_manager)?; + let ledger = get_ledger_from_info(remote_wallet_info, keypair_name, wallet_manager)?; Ok(RemoteKeypair::new( RemoteWalletType::Ledger(ledger), derivation_path, diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index 68c0aac3b..69a02d8f7 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -18,10 +18,10 @@ const HID_GLOBAL_USAGE_PAGE: u16 = 0xFF00; const HID_USB_DEVICE_CLASS: u8 = 0; /// Remote wallet error. -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum RemoteWalletError { #[error("hidapi error")] - Hid(#[from] hidapi::HidError), + Hid(String), #[error("device type mismatch")] DeviceTypeMismatch, @@ -51,12 +51,16 @@ pub enum RemoteWalletError { UserCancel, } +impl From for RemoteWalletError { + fn from(err: hidapi::HidError) -> RemoteWalletError { + RemoteWalletError::Hid(err.to_string()) + } +} + impl From for SignerError { fn from(err: RemoteWalletError) -> SignerError { match err { - RemoteWalletError::Hid(hid_error) => { - SignerError::ConnectionError(hid_error.to_string()) - } + RemoteWalletError::Hid(hid_error) => SignerError::ConnectionError(hid_error), RemoteWalletError::DeviceTypeMismatch => SignerError::ConnectionError(err.to_string()), RemoteWalletError::InvalidDevice => SignerError::ConnectionError(err.to_string()), RemoteWalletError::InvalidInput(input) => SignerError::InvalidInput(input), @@ -215,6 +219,8 @@ pub struct RemoteWalletInfo { pub serial: String, /// Base pubkey of device at Solana derivation path pub pubkey: Pubkey, + /// Initial read error + pub error: Option, } impl RemoteWalletInfo { @@ -360,6 +366,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey, + error: None, })); assert_eq!( derivation_path, @@ -376,6 +383,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey, + error: None, })); assert_eq!( derivation_path, @@ -392,6 +400,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey, + error: None, })); assert_eq!( derivation_path, @@ -408,6 +417,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey, + error: None, })); assert_eq!( derivation_path, @@ -424,6 +434,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey, + error: None, })); assert_eq!( derivation_path, @@ -441,6 +452,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey: Pubkey::default(), + error: None, })); assert_eq!( derivation_path, @@ -456,6 +468,7 @@ mod tests { manufacturer: "ledger".to_string(), serial: "".to_string(), pubkey: Pubkey::default(), + error: None, })); assert_eq!( derivation_path, @@ -490,6 +503,7 @@ mod tests { model: "Nano S".to_string(), serial: "0001".to_string(), pubkey: pubkey.clone(), + error: None, }; let mut test_info = RemoteWalletInfo::default(); test_info.manufacturer = "Not Ledger".to_string();