From b66c9729006169d506ad0a6f1f151883cacfb863 Mon Sep 17 00:00:00 2001 From: Boris Oncev Date: Tue, 5 Aug 2025 12:32:55 +0200 Subject: [PATCH] Add ledger support in CLI and GUI wallet --- node-gui/backend/Cargo.toml | 1 + node-gui/backend/src/backend_impl.rs | 78 +++- node-gui/backend/src/error.rs | 4 +- node-gui/src/main_window/main_menu.rs | 22 + .../main_widget/tabs/wallet/left_panel.rs | 86 ++-- .../main_widget/tabs/wallet/mod.rs | 17 +- node-gui/src/main_window/mod.rs | 30 +- wallet/src/key_chain/master_key_chain/mod.rs | 12 +- wallet/src/signer/software_signer/mod.rs | 7 +- wallet/src/wallet/mod.rs | 42 +- wallet/src/wallet/test_helpers.rs | 9 +- wallet/src/wallet/tests.rs | 38 +- wallet/storage/Cargo.toml | 3 +- wallet/types/src/wallet_type.rs | 9 + .../src/command_handler/mod.rs | 9 + wallet/wallet-cli-commands/src/lib.rs | 37 ++ wallet/wallet-cli-lib/Cargo.toml | 25 +- wallet/wallet-cli/Cargo.toml | 17 +- wallet/wallet-controller/src/lib.rs | 88 +++- .../wallet-controller/src/runtime_wallet.rs | 411 ++++++++++++++++++ wallet/wallet-controller/src/types/mod.rs | 11 + wallet/wallet-node-client/Cargo.toml | 11 +- .../src/rpc_client/client_impl.rs | 4 + wallet/wallet-rpc-daemon/Cargo.toml | 9 +- wallet/wallet-rpc-daemon/docs/RPC.md | 9 +- wallet/wallet-rpc-lib/src/lib.rs | 6 +- wallet/wallet-rpc-lib/src/rpc/mod.rs | 4 +- wallet/wallet-rpc-lib/src/rpc/types.rs | 4 + wallet/wallet-rpc-lib/tests/utils.rs | 7 +- 29 files changed, 879 insertions(+), 131 deletions(-) diff --git a/node-gui/backend/Cargo.toml b/node-gui/backend/Cargo.toml index 77dfa3c99e..a94afe1352 100644 --- a/node-gui/backend/Cargo.toml +++ b/node-gui/backend/Cargo.toml @@ -65,3 +65,4 @@ ledger = [ "wallet-rpc-client/ledger", "wallet-cli-commands/ledger", ] +default = ["trezor", "ledger"] diff --git a/node-gui/backend/src/backend_impl.rs b/node-gui/backend/src/backend_impl.rs index d2d1f0709d..22ec60b70d 100644 --- a/node-gui/backend/src/backend_impl.rs +++ b/node-gui/backend/src/backend_impl.rs @@ -354,9 +354,43 @@ impl Backend { (wallet_data, accounts_info, best_block) } + #[cfg(feature = "ledger")] + (WalletType::Ledger, ColdHotNodeController::Hot(controller)) => { + let handles_client = WalletHandlesClient::new( + controller.chainstate.clone(), + controller.mempool.clone(), + controller.block_prod.clone(), + controller.p2p.clone(), + ) + .await + .map_err(|e| BackendError::WalletError(e.to_string()))?; + + let (wallet_rpc, command_handler, best_block, accounts_info, accounts_data) = self + .create_wallet( + handles_client, + file_path.clone(), + wallet_args, + import, + wallet_events, + ) + .await?; + + let wallet_data = WalletData { + controller: GuiHotColdController::Hot(wallet_rpc, command_handler), + accounts: accounts_data, + best_block, + updated: false, + }; + + (wallet_data, accounts_info, best_block) + } #[cfg(feature = "trezor")] (WalletType::Trezor, ColdHotNodeController::Cold) => { - return Err(BackendError::ColdTrezorNotSupported) + return Err(BackendError::HardwareWalletNotSupportedInColdMode) + } + #[cfg(feature = "ledger")] + (WalletType::Ledger, ColdHotNodeController::Cold) => { + return Err(BackendError::HardwareWalletNotSupportedInColdMode) } (WalletType::Hot, ColdHotNodeController::Cold) => { return Err(BackendError::HotNotSupported) @@ -593,9 +627,49 @@ impl Backend { (wallet_data, accounts_info, best_block, encryption_state) } + #[cfg(feature = "ledger")] + (WalletType::Ledger, ColdHotNodeController::Hot(controller)) => { + let handles_client = WalletHandlesClient::new( + controller.chainstate.clone(), + controller.mempool.clone(), + controller.block_prod.clone(), + controller.p2p.clone(), + ) + .await + .map_err(|e| BackendError::WalletError(e.to_string()))?; + + let ( + wallet_rpc, + command_handler, + encryption_state, + best_block, + accounts_info, + accounts_data, + ) = self + .open_wallet( + handles_client, + file_path.clone(), + wallet_events, + Some(HardwareWalletType::Ledger), + ) + .await?; + + let wallet_data = WalletData { + controller: GuiHotColdController::Hot(wallet_rpc, command_handler), + accounts: accounts_data, + best_block, + updated: false, + }; + + (wallet_data, accounts_info, best_block, encryption_state) + } #[cfg(feature = "trezor")] (WalletType::Trezor, ColdHotNodeController::Cold) => { - return Err(BackendError::ColdTrezorNotSupported) + return Err(BackendError::HardwareWalletNotSupportedInColdMode) + } + #[cfg(feature = "ledger")] + (WalletType::Ledger, ColdHotNodeController::Cold) => { + return Err(BackendError::HardwareWalletNotSupportedInColdMode) } (WalletType::Hot, ColdHotNodeController::Cold) => { return Err(BackendError::HotNotSupported) diff --git a/node-gui/backend/src/error.rs b/node-gui/backend/src/error.rs index 4d170094d1..42fc4d9525 100644 --- a/node-gui/backend/src/error.rs +++ b/node-gui/backend/src/error.rs @@ -46,8 +46,8 @@ pub enum BackendError { ColdWallet, #[error("Cannot interact with a hot wallet when in Cold wallet mode")] HotNotSupported, - #[error("Cannot use a Trezor wallet in a Cold wallet mode")] - ColdTrezorNotSupported, + #[error("Cannot use a Hardware wallet in a Cold wallet mode")] + HardwareWalletNotSupportedInColdMode, #[error("Invalid console command: {0}")] InvalidConsoleCommand(String), #[error("Empty console command")] diff --git a/node-gui/src/main_window/main_menu.rs b/node-gui/src/main_window/main_menu.rs index 113be444ad..1600ffc23d 100644 --- a/node-gui/src/main_window/main_menu.rs +++ b/node-gui/src/main_window/main_menu.rs @@ -125,6 +125,27 @@ fn make_menu_file<'a>(wallet_mode: WalletMode) -> Item<'a, MenuMessage, Theme, i }, ), ]; + #[cfg(feature = "ledger")] + { + menu.push(menu_item( + "(Beta) Create new Ledger wallet", + MenuMessage::CreateNewWallet { + wallet_type: WalletType::Ledger, + }, + )); + menu.push(menu_item( + "(Beta) Recover from Ledger wallet", + MenuMessage::RecoverWallet { + wallet_type: WalletType::Ledger, + }, + )); + menu.push(menu_item( + "(Beta) Open Ledger wallet", + MenuMessage::OpenWallet { + wallet_type: WalletType::Ledger, + }, + )); + } #[cfg(feature = "trezor")] { menu.push(menu_item( @@ -146,6 +167,7 @@ fn make_menu_file<'a>(wallet_mode: WalletMode) -> Item<'a, MenuMessage, Theme, i }, )); } + // TODO: enable setting when needed // menu.push(menu_item("Settings", MenuMessage::NoOp)); menu.push(menu_item("Exit", MenuMessage::Exit)); diff --git a/node-gui/src/main_window/main_widget/tabs/wallet/left_panel.rs b/node-gui/src/main_window/main_widget/tabs/wallet/left_panel.rs index a00d1cf3ad..757339ca19 100644 --- a/node-gui/src/main_window/main_widget/tabs/wallet/left_panel.rs +++ b/node-gui/src/main_window/main_widget/tabs/wallet/left_panel.rs @@ -118,17 +118,48 @@ pub fn view_left_panel( .on_press(WalletMessage::SelectPanel(panel)) .padding(panel_button_row_padding) }; + let is_cold_wallet = wallet_info.wallet_type == WalletType::Cold; // `next_height` is used to prevent flickering when a new block is found - let show_scan_progress = match wallet_info.wallet_type { - WalletType::Cold => false, - #[cfg(feature = "trezor")] - WalletType::Trezor => { - wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height - } - WalletType::Hot => { - wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height - } + let show_scan_progress = if is_cold_wallet { + false + } else { + wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height + }; + + let hardware_wallet_panels = || { + column![ + panel_button( + "Transactions", + SelectedPanel::Transactions, + selected_panel, + TRANSACTIONS_TOOLTIP_TEXT + ), + panel_button( + "Addresses", + SelectedPanel::Addresses, + selected_panel, + ADDRESSES_TOOLTIP_TEXT + ), + panel_button( + "Send", + SelectedPanel::Send, + selected_panel, + SEND_TOOLTIP_TEXT + ), + panel_button( + "Delegation", + SelectedPanel::Delegation, + selected_panel, + DELEGATION_TOOLTIP_TEXT + ), + panel_button( + "Console", + SelectedPanel::Console, + selected_panel, + CONSOLE_TOOLTIP_TEXT, + ) + ] }; let scan_progress_widget = if show_scan_progress { @@ -188,38 +219,11 @@ pub fn view_left_panel( match wallet_info.wallet_type { #[cfg(feature = "trezor")] WalletType::Trezor => { - column![ - panel_button( - "Transactions", - SelectedPanel::Transactions, - selected_panel, - TRANSACTIONS_TOOLTIP_TEXT - ), - panel_button( - "Addresses", - SelectedPanel::Addresses, - selected_panel, - ADDRESSES_TOOLTIP_TEXT - ), - panel_button( - "Send", - SelectedPanel::Send, - selected_panel, - SEND_TOOLTIP_TEXT - ), - panel_button( - "Delegation", - SelectedPanel::Delegation, - selected_panel, - DELEGATION_TOOLTIP_TEXT - ), - panel_button( - "Console", - SelectedPanel::Console, - selected_panel, - CONSOLE_TOOLTIP_TEXT, - ) - ] + hardware_wallet_panels() + } + #[cfg(feature = "ledger")] + WalletType::Ledger => { + hardware_wallet_panels() } WalletType::Cold => { column![ diff --git a/node-gui/src/main_window/main_widget/tabs/wallet/mod.rs b/node-gui/src/main_window/main_widget/tabs/wallet/mod.rs index 1938050926..e88d8c7521 100644 --- a/node-gui/src/main_window/main_widget/tabs/wallet/mod.rs +++ b/node-gui/src/main_window/main_widget/tabs/wallet/mod.rs @@ -183,6 +183,8 @@ impl WalletTab { WalletType::Cold => SelectedPanel::Addresses, #[cfg(feature = "trezor")] WalletType::Trezor => SelectedPanel::Transactions, + #[cfg(feature = "ledger")] + WalletType::Ledger => SelectedPanel::Transactions, }; WalletTab { @@ -490,15 +492,12 @@ impl Tab for WalletTab { .get(&self.selected_account) .expect("selected account must be known"); - let still_syncing = match wallet_info.wallet_type { - WalletType::Cold => false, - #[cfg(feature = "trezor")] - WalletType::Trezor => { - wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height - } - WalletType::Hot => { - wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height - } + let is_cold_wallet = wallet_info.wallet_type == WalletType::Cold; + + let still_syncing = if is_cold_wallet { + false + } else { + wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height } .then_some(WalletMessage::StillSyncing); diff --git a/node-gui/src/main_window/mod.rs b/node-gui/src/main_window/mod.rs index f8561a16c9..8109f3476f 100644 --- a/node-gui/src/main_window/mod.rs +++ b/node-gui/src/main_window/mod.rs @@ -38,7 +38,7 @@ use wallet_cli_commands::ConsoleCommand; use wallet_controller::types::WalletTypeArgs; use wallet_types::{seed_phrase::StoreSeedPhrase, wallet_type::WalletType, ImportOrCreate}; -#[cfg(feature = "trezor")] +#[cfg(any(feature = "trezor", feature = "ledger"))] use crate::widgets::create_hw_wallet::hw_wallet_create_dialog; use crate::{ main_window::{main_menu::MenuMessage, main_widget::MainWidgetMessage}, @@ -148,6 +148,8 @@ pub enum WalletArgs { }, #[cfg(feature = "trezor")] Trezor, + #[cfg(feature = "ledger")] + Ledger, } impl From<&WalletArgs> for WalletType { @@ -165,6 +167,8 @@ impl From<&WalletArgs> for WalletType { } #[cfg(feature = "trezor")] WalletArgs::Trezor => WalletType::Trezor, + #[cfg(feature = "ledger")] + WalletArgs::Ledger => WalletType::Ledger, } } } @@ -291,6 +295,8 @@ impl MainWindow { }, #[cfg(feature = "trezor")] WalletType::Trezor => WalletArgs::Trezor, + #[cfg(feature = "ledger")] + WalletType::Ledger => WalletArgs::Ledger, }; self.active_dialog = ActiveDialog::WalletCreate { wallet_args }; Task::none() @@ -733,6 +739,8 @@ impl MainWindow { } #[cfg(feature = "trezor")] WalletArgs::Trezor => WalletTypeArgs::Trezor { device_id: None }, + #[cfg(feature = "ledger")] + WalletArgs::Ledger => WalletTypeArgs::Ledger, }; self.file_dialog_active = true; @@ -864,6 +872,16 @@ impl MainWindow { ImportOrCreate::Create, ) .into(), + #[cfg(feature = "ledger")] + WalletArgs::Ledger => hw_wallet_create_dialog( + Box::new(move || MainWindowMessage::ImportWalletMnemonic { + args: WalletArgs::Ledger, + import: ImportOrCreate::Create, + }), + Box::new(|| MainWindowMessage::CloseDialog), + ImportOrCreate::Create, + ) + .into(), }, ActiveDialog::WalletRecover { wallet_type } => { let is_cold = *wallet_type == WalletType::Cold; @@ -888,6 +906,16 @@ impl MainWindow { ImportOrCreate::Import, ) .into(), + #[cfg(feature = "ledger")] + WalletType::Ledger => hw_wallet_create_dialog( + Box::new(move || MainWindowMessage::ImportWalletMnemonic { + args: WalletArgs::Ledger, + import: ImportOrCreate::Create, + }), + Box::new(|| MainWindowMessage::CloseDialog), + ImportOrCreate::Import, + ) + .into(), } } diff --git a/wallet/src/key_chain/master_key_chain/mod.rs b/wallet/src/key_chain/master_key_chain/mod.rs index 86c973d191..5eb67449c0 100644 --- a/wallet/src/key_chain/master_key_chain/mod.rs +++ b/wallet/src/key_chain/master_key_chain/mod.rs @@ -21,8 +21,8 @@ use crypto::key::hdkd::u31::U31; use crypto::vrf::ExtendedVRFPrivateKey; use std::sync::Arc; use wallet_storage::{ - StoreTxRwUnlocked, WalletStorageReadLocked, WalletStorageReadUnlocked, - WalletStorageWriteLocked, WalletStorageWriteUnlocked, + WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteLocked, + WalletStorageWriteUnlocked, }; use wallet_types::seed_phrase::{SerializableSeedPhrase, StoreSeedPhrase}; @@ -59,9 +59,9 @@ impl MasterKeyChain { )) } - pub fn new_from_mnemonic( + pub fn new_from_mnemonic( chain_config: Arc, - db_tx: &mut StoreTxRwUnlocked, + db_tx: &mut impl WalletStorageWriteUnlocked, mnemonic_str: &str, passphrase: Option<&str>, save_seed_phrase: StoreSeedPhrase, @@ -80,9 +80,9 @@ impl MasterKeyChain { ) } - fn new_from_root_key( + fn new_from_root_key( chain_config: Arc, - db_tx: &mut StoreTxRwUnlocked, + db_tx: &mut impl WalletStorageWriteUnlocked, root_key: ExtendedPrivateKey, root_vrf_key: ExtendedVRFPrivateKey, seed_phrase: Option, diff --git a/wallet/src/signer/software_signer/mod.rs b/wallet/src/signer/software_signer/mod.rs index 35a1397cdc..63eb52b0ee 100644 --- a/wallet/src/signer/software_signer/mod.rs +++ b/wallet/src/signer/software_signer/mod.rs @@ -50,8 +50,7 @@ use crypto::key::{ }; use randomness::make_true_rng; use wallet_storage::{ - StoreTxRwUnlocked, WalletStorageReadLocked, WalletStorageReadUnlocked, - WalletStorageWriteUnlocked, + WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteUnlocked, }; use wallet_types::{ hw_data::HardwareWalletFullInfo, @@ -453,9 +452,9 @@ pub struct SoftwareSignerProvider { } impl SoftwareSignerProvider { - pub fn new_from_mnemonic( + pub fn new_from_mnemonic( chain_config: Arc, - db_tx: &mut StoreTxRwUnlocked, + db_tx: &mut impl WalletStorageWriteUnlocked, mnemonic_str: &str, passphrase: Option<&str>, save_seed_phrase: StoreSeedPhrase, diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index ddaaf9d887..d0e595674f 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -269,8 +269,10 @@ pub enum WalletError { "A VRF public key must be specified when creating a staking pool using a hardware wallet" )] VrfKeyMustBeProvided, - #[error("Cannot change a Trezor wallet type")] - CannotChangeTrezorWalletType, + #[error("Cannot change a {from} wallet type to {to}")] + CannotChangeWalletType { from: WalletType, to: WalletType }, + #[error("Cannot change a Ledger wallet type")] + CannotChangeLedgerWalletType, #[error("Missing additional data for Pool {0}")] MissingPoolAdditionalData(PoolId), #[error("Missing additional data for Token {0}")] @@ -357,7 +359,7 @@ where B: storage::BackendWithSendableTransactions + 'static, P: SignerProvider, { - pub async fn create_new_wallet) -> WalletResult

>( + pub async fn create_new_wallet) -> WalletResult

>( chain_config: Arc, db: Store, best_block: (BlockHeight, Id), @@ -373,7 +375,7 @@ where Ok(wallet) } - pub async fn recover_wallet) -> WalletResult

>( + pub async fn recover_wallet) -> WalletResult

>( chain_config: Arc, db: Store, wallet_type: WalletType, @@ -382,7 +384,7 @@ where Self::new_wallet(chain_config, db, wallet_type, signer_provider).await } - async fn new_wallet) -> WalletResult

>( + async fn new_wallet) -> WalletResult

>( chain_config: Arc, mut db: Store, wallet_type: WalletType, @@ -394,7 +396,7 @@ where db_tx.set_chain_info(&ChainInfo::new(chain_config.as_ref()))?; db_tx.set_lookahead_size(LOOKAHEAD_SIZE)?; db_tx.set_wallet_type(wallet_type)?; - let mut signer_provider = match signer_provider(&mut db_tx) { + let mut signer_provider = match signer_provider(&mut db_tx).await { Ok(x) => x, #[cfg(feature = "trezor")] Err(WalletError::SignerError(SignerError::TrezorError( @@ -598,7 +600,7 @@ where /// Check the wallet DB version and perform any migrations needed async fn check_and_migrate_db< F: Fn(u32) -> Result<(), WalletError>, - F2: FnOnce(&StoreTxRo) -> WalletResult

, + F2: AsyncFnOnce(StoreTxRo) -> WalletResult

, >( db: &mut Store, chain_config: Arc, @@ -611,7 +613,7 @@ where version != WALLET_VERSION_UNINITIALIZED, WalletError::WalletNotInitialized ); - let mut signer_provider = signer_provider(&db.transaction_ro()?)?; + let mut signer_provider = signer_provider(db.transaction_ro()?).await?; loop { let version = db.transaction_ro()?.get_storage_version()?; @@ -707,12 +709,32 @@ where #[cfg(feature = "trezor")] (WalletType::Cold | WalletType::Hot, WalletType::Trezor) | (WalletType::Trezor, WalletType::Hot | WalletType::Cold) => { - return Err(WalletError::CannotChangeTrezorWalletType) + return Err(WalletError::CannotChangeWalletType { + from: current_wallet_type, + to: wallet_type, + }) + } + #[cfg(all(feature = "trezor", feature = "ledger"))] + (WalletType::Ledger, WalletType::Trezor) | (WalletType::Trezor, WalletType::Ledger) => { + return Err(WalletError::CannotChangeWalletType { + from: current_wallet_type, + to: wallet_type, + }) + } + #[cfg(feature = "ledger")] + (WalletType::Cold | WalletType::Hot, WalletType::Ledger) + | (WalletType::Ledger, WalletType::Hot | WalletType::Cold) => { + return Err(WalletError::CannotChangeWalletType { + from: current_wallet_type, + to: wallet_type, + }) } (WalletType::Cold, WalletType::Cold) => {} (WalletType::Hot, WalletType::Hot) => {} #[cfg(feature = "trezor")] (WalletType::Trezor, WalletType::Trezor) => {} + #[cfg(feature = "trezor")] + (WalletType::Ledger, WalletType::Ledger) => {} } Ok(()) } @@ -824,7 +846,7 @@ where pub async fn load_wallet< F: Fn(u32) -> WalletResult<()>, - F2: FnOnce(&StoreTxRo) -> WalletResult

, + F2: AsyncFnOnce(StoreTxRo) -> WalletResult

, >( chain_config: Arc, mut db: Store, diff --git a/wallet/src/wallet/test_helpers.rs b/wallet/src/wallet/test_helpers.rs index 1e9efda4a8..dc3b90cf59 100644 --- a/wallet/src/wallet/test_helpers.rs +++ b/wallet/src/wallet/test_helpers.rs @@ -45,7 +45,7 @@ pub async fn create_wallet_with_mnemonic( db, (BlockHeight::new(0), genesis_block_id), WalletType::Hot, - |db_tx| { + async |db_tx| { Ok(SoftwareSignerProvider::new_from_mnemonic( chain_config, db_tx, @@ -81,14 +81,15 @@ pub async fn create_wallet_with_mnemonic_and_named_db( db, (BlockHeight::new(0), genesis_block_id), WalletType::Hot, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config, db_tx, mnemonic, None, StoreSeedPhrase::DoNotStore, - )?) + ) + .map_err(Into::into) }, ) .await diff --git a/wallet/src/wallet/tests.rs b/wallet/src/wallet/tests.rs index 674f74767e..3633ded5fa 100644 --- a/wallet/src/wallet/tests.rs +++ b/wallet/src/wallet/tests.rs @@ -284,7 +284,7 @@ async fn verify_wallet_balance( |_| Ok(()), WalletControllerMode::Hot, false, - |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), db_tx), + async |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), &db_tx), ) .await .unwrap() @@ -392,7 +392,7 @@ async fn wallet_creation_in_memory() { |_| Ok(()), WalletControllerMode::Hot, false, - |db_tx| SoftwareSignerProvider::load_from_database(chain_config2, db_tx), + async |db_tx| SoftwareSignerProvider::load_from_database(chain_config2, &db_tx), ) .await { @@ -412,7 +412,7 @@ async fn wallet_creation_in_memory() { |_| Ok(()), WalletControllerMode::Hot, false, - |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), db_tx), + async |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), &db_tx), ) .await .unwrap(); @@ -447,14 +447,15 @@ async fn wallet_migration_to_v2(#[case] seed: Seed) { db, (BlockHeight::new(0), genesis_block_id), WalletType::Hot, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, MNEMONIC, None, StoreSeedPhrase::DoNotStore, - )?) + ) + .map_err(Into::into) }, ) .await @@ -515,7 +516,7 @@ async fn wallet_migration_to_v2(#[case] seed: Seed) { |_| Ok(()), WalletControllerMode::Hot, false, - |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), db_tx), + async |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), &db_tx), ) .await .unwrap() @@ -570,14 +571,15 @@ async fn wallet_seed_phrase_retrieval(#[case] seed: Seed) { db, (BlockHeight::new(0), genesis_block_id), WalletType::Hot, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, MNEMONIC, wallet_passphrase.as_ref().map(|p| p.as_ref()), StoreSeedPhrase::Store, - )?) + ) + .map_err(Into::into) }, ) .await @@ -668,14 +670,15 @@ async fn wallet_seed_phrase_check_address() { db, (BlockHeight::new(0), genesis_block_id), WalletType::Hot, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, MNEMONIC, wallet_passphrase.as_ref().map(|p| p.as_ref()), StoreSeedPhrase::Store, - )?) + ) + .map_err(Into::into) }, ) .await @@ -712,14 +715,15 @@ async fn wallet_seed_phrase_check_address() { db, (BlockHeight::new(0), genesis_block_id), WalletType::Hot, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, MNEMONIC, wallet_passphrase.as_ref().map(|p| p.as_ref()), StoreSeedPhrase::Store, - )?) + ) + .map_err(Into::into) }, ) .await @@ -1093,7 +1097,7 @@ async fn test_wallet_accounts( |_| Ok(()), WalletControllerMode::Hot, false, - |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), db_tx), + async |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), &db_tx), ) .await .unwrap() diff --git a/wallet/storage/Cargo.toml b/wallet/storage/Cargo.toml index 6c28bd9e19..7fe01f88c4 100644 --- a/wallet/storage/Cargo.toml +++ b/wallet/storage/Cargo.toml @@ -28,4 +28,5 @@ rstest.workspace = true [features] trezor = ["wallet-types/trezor"] -default = ["trezor"] +ledger = ["wallet-types/ledger"] +default = ["trezor", "ledger"] diff --git a/wallet/types/src/wallet_type.rs b/wallet/types/src/wallet_type.rs index 2b84a0a3b5..d46fda23bc 100644 --- a/wallet/types/src/wallet_type.rs +++ b/wallet/types/src/wallet_type.rs @@ -25,6 +25,9 @@ pub enum WalletType { #[cfg(feature = "trezor")] #[codec(index = 2)] Trezor, + #[cfg(feature = "ledger")] + #[codec(index = 3)] + Ledger, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -56,6 +59,10 @@ impl WalletType { (Self::Trezor, WalletControllerMode::Hot) => true, #[cfg(feature = "trezor")] (Self::Trezor, WalletControllerMode::Cold) => false, + #[cfg(feature = "ledger")] + (Self::Ledger, WalletControllerMode::Hot) => true, + #[cfg(feature = "ledger")] + (Self::Ledger, WalletControllerMode::Cold) => false, } } } @@ -76,6 +83,8 @@ impl Display for WalletType { Self::Cold => write!(f, "Cold"), #[cfg(feature = "trezor")] Self::Trezor => write!(f, "Trezor"), + #[cfg(feature = "ledger")] + Self::Ledger => write!(f, "Ledger"), } } } diff --git a/wallet/wallet-cli-commands/src/command_handler/mod.rs b/wallet/wallet-cli-commands/src/command_handler/mod.rs index fda3a57710..1815b477cf 100644 --- a/wallet/wallet-cli-commands/src/command_handler/mod.rs +++ b/wallet/wallet-cli-commands/src/command_handler/mod.rs @@ -253,6 +253,15 @@ where false, Some(HardwareWalletType::Trezor { device_id }), ), + OpenWalletSubCommand::Ledger { + wallet_path, + encryption_password, + } => ( + wallet_path, + encryption_password, + false, + Some(HardwareWalletType::Ledger), + ), }; let response = self diff --git a/wallet/wallet-cli-commands/src/lib.rs b/wallet/wallet-cli-commands/src/lib.rs index 59672720cf..46009caa26 100644 --- a/wallet/wallet-cli-commands/src/lib.rs +++ b/wallet/wallet-cli-commands/src/lib.rs @@ -94,6 +94,18 @@ pub enum CreateWalletSubCommand { #[arg(long)] device_id: Option, }, + /// (Beta) Create a wallet using a connected Ledger wallet. + /// + /// Only the public keys will be kept in the wallet file. + /// + /// Cannot specify a mnemonic or passphrase here, both are managed on the device. + /// Depending on its configuration, the passphrase may need to be entered manually + /// each time or may be applied automatically after unlocking with a secondary PIN. + #[command()] + Ledger { + /// File path of the wallet file + wallet_path: PathBuf, + }, } impl CreateWalletSubCommand { @@ -120,6 +132,8 @@ impl CreateWalletSubCommand { wallet_path, device_id, } => (wallet_path, WalletTypeArgs::Trezor { device_id }), + + Self::Ledger { wallet_path } => (wallet_path, WalletTypeArgs::Ledger), } } } @@ -169,6 +183,18 @@ pub enum RecoverWalletSubCommand { #[arg(long)] device_id: Option, }, + /// (Beta) Recover a wallet using a connected Ledger hardware wallet. + /// + /// Only the public keys will be kept in the wallet file. + /// + /// Cannot specify a mnemonic or passphrase here, both are managed on the device. + /// Depending on its configuration, the passphrase may need to be entered manually + /// each time or may be applied automatically after unlocking with a secondary PIN. + #[command()] + Ledger { + /// File path of the wallet file + wallet_path: PathBuf, + }, } impl RecoverWalletSubCommand { @@ -195,6 +221,8 @@ impl RecoverWalletSubCommand { wallet_path, device_id, } => (wallet_path, WalletTypeArgs::Trezor { device_id }), + + Self::Ledger { wallet_path } => (wallet_path, WalletTypeArgs::Ledger), } } } @@ -227,6 +255,15 @@ pub enum OpenWalletSubCommand { #[arg(long)] device_id: Option, }, + + /// (Beta) Open a wallet file that is connected to a Ledger hardware wallet. + #[command()] + Ledger { + /// File path of the wallet file + wallet_path: PathBuf, + /// The existing password, if the wallet is encrypted. + encryption_password: Option, + }, } #[derive(Debug, Parser)] diff --git a/wallet/wallet-cli-lib/Cargo.toml b/wallet/wallet-cli-lib/Cargo.toml index 0c7836a6a6..a08641f07d 100644 --- a/wallet/wallet-cli-lib/Cargo.toml +++ b/wallet/wallet-cli-lib/Cargo.toml @@ -40,7 +40,13 @@ reedline = { workspace = true, features = ["external_printer"] } serde_json.workspace = true shlex.workspace = true thiserror.workspace = true -tokio = { workspace = true, default-features = false, features = ["io-util", "macros", "net", "rt", "sync"] } +tokio = { workspace = true, default-features = false, features = [ + "io-util", + "macros", + "net", + "rt", + "sync", +] } futures.workspace = true prettytable-rs = "0.10" @@ -59,5 +65,18 @@ wallet-test-node = { path = "../wallet-test-node" } rstest.workspace = true [features] -trezor = ["wallet/trezor", "wallet-cli-commands/trezor", "wallet-types/trezor", "wallet-rpc-lib/trezor", "wallet-rpc-client/trezor"] -default = ["trezor"] +trezor = [ + "wallet/trezor", + "wallet-cli-commands/trezor", + "wallet-types/trezor", + "wallet-rpc-lib/trezor", + "wallet-rpc-client/trezor", +] +ledger = [ + "wallet/ledger", + "wallet-cli-commands/ledger", + "wallet-types/ledger", + "wallet-rpc-lib/ledger", + "wallet-rpc-client/ledger", +] +default = ["trezor", "ledger"] diff --git a/wallet/wallet-cli/Cargo.toml b/wallet/wallet-cli/Cargo.toml index b1ca0315ad..4d35b41a9d 100644 --- a/wallet/wallet-cli/Cargo.toml +++ b/wallet/wallet-cli/Cargo.toml @@ -4,7 +4,11 @@ license.workspace = true edition.workspace = true version.workspace = true rust-version.workspace = true -authors = ["Samer Afach ", "Ben Marsh ", "Enrico Rubboli "] +authors = [ + "Samer Afach ", + "Ben Marsh ", + "Enrico Rubboli ", +] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,8 +17,15 @@ utils = { path = "../../utils" } wallet-cli-lib = { path = "../wallet-cli-lib" } clap = { workspace = true, features = ["derive"] } -tokio = { workspace = true, default-features = false, features = ["io-util", "macros", "net", "rt", "sync"] } +tokio = { workspace = true, default-features = false, features = [ + "io-util", + "macros", + "net", + "rt", + "sync", +] } [features] trezor = ["wallet-cli-lib/trezor"] -default = ["trezor"] +ledger = ["wallet-cli-lib/ledger"] +default = ["trezor", "ledger"] diff --git a/wallet/wallet-controller/src/lib.rs b/wallet/wallet-controller/src/lib.rs index 2c3901f80c..953f0810a8 100644 --- a/wallet/wallet-controller/src/lib.rs +++ b/wallet/wallet-controller/src/lib.rs @@ -86,6 +86,8 @@ pub use node_comm::{ rpc_client::NodeRpcClient, }; use randomness::{make_pseudo_rng, make_true_rng, Rng}; +#[cfg(feature = "ledger")] +use wallet::signer::ledger_signer::LedgerSignerProvider; #[cfg(feature = "trezor")] use wallet::signer::trezor_signer::TrezorSignerProvider; #[cfg(feature = "trezor")] @@ -288,14 +290,15 @@ where db, best_block, wallet_type, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, &mnemonic.to_string(), passphrase_ref, store_seed_phrase, - )?) + ) + .map_err(Into::into) }, ) .await @@ -308,16 +311,28 @@ where db, best_block, wallet_type, - |_db_tx| { - Ok(TrezorSignerProvider::new( + async |_db_tx| { + TrezorSignerProvider::new( device_id.map(|device_id| SelectedDevice { device_id }), ) - .map_err(SignerError::TrezorError)?) + .map_err(SignerError::TrezorError) + .map_err(Into::into) }, ) .await .map_err(ControllerError::WalletError) .map(|w| w.map_wallet(RuntimeWallet::Trezor)), + #[cfg(feature = "ledger")] + WalletTypeArgsComputed::Ledger => wallet::Wallet::create_new_wallet( + Arc::clone(&chain_config), + db, + best_block, + wallet_type, + async |_db_tx| LedgerSignerProvider::new().await.map_err(Into::into), + ) + .await + .map_err(ControllerError::WalletError) + .map(|w| w.map_wallet(RuntimeWallet::Ledger)), }; Self::delete_wallet_file_on_wallet_creation_failure(&res, file_path); @@ -353,14 +368,15 @@ where Arc::clone(&chain_config), db, wallet_type, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, &mnemonic.to_string(), passphrase_ref, store_seed_phrase, - )?) + ) + .map_err(Into::into) }, ) .await @@ -373,17 +389,30 @@ where Arc::clone(&chain_config), db, wallet_type, - |_db_tx| { - Ok(TrezorSignerProvider::new( + async |_db_tx| { + TrezorSignerProvider::new( device_id.map(|device_id| SelectedDevice { device_id }), ) - .map_err(SignerError::TrezorError)?) + .map_err(SignerError::TrezorError) + .map_err(Into::into) }, ) .await .map_err(ControllerError::WalletError)?; Ok(wallet.map_wallet(RuntimeWallet::Trezor)) } + #[cfg(feature = "ledger")] + WalletTypeArgsComputed::Ledger => { + let wallet = wallet::Wallet::recover_wallet( + Arc::clone(&chain_config), + db, + wallet_type, + async |_db_tx| LedgerSignerProvider::new().await.map_err(Into::into), + ) + .await + .map_err(ControllerError::WalletError)?; + Ok(wallet.map_wallet(RuntimeWallet::Ledger)) + } }; Self::delete_wallet_file_on_wallet_creation_failure(&res, file_path); @@ -468,7 +497,9 @@ where |version| Self::make_backup_wallet_file(file_path.as_ref(), version), current_controller_mode, force_change_wallet_type, - |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), db_tx), + async |db_tx| { + SoftwareSignerProvider::load_from_database(chain_config.clone(), &db_tx) + }, ) .await .map_err(ControllerError::WalletError)?; @@ -483,10 +514,10 @@ where |version| Self::make_backup_wallet_file(file_path.as_ref(), version), current_controller_mode, force_change_wallet_type, - |db_tx| { + async |db_tx| { TrezorSignerProvider::load_from_database( chain_config.clone(), - db_tx, + &db_tx, device_id, ) }, @@ -495,6 +526,24 @@ where .map_err(ControllerError::WalletError)?; Ok(wallet.map_wallet(RuntimeWallet::Trezor)) } + #[cfg(feature = "ledger")] + WalletType::Ledger => { + let wallet = wallet::Wallet::load_wallet( + Arc::clone(&chain_config), + db, + password, + |version| Self::make_backup_wallet_file(file_path.as_ref(), version), + current_controller_mode, + force_change_wallet_type, + async |mut db_tx| { + LedgerSignerProvider::load_from_database(chain_config.clone(), &mut db_tx) + .await + }, + ) + .await + .map_err(ControllerError::WalletError)?; + Ok(wallet.map_wallet(RuntimeWallet::Ledger)) + } } } @@ -848,6 +897,10 @@ where RuntimeWallet::Trezor(w) => { sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events).await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events).await + } }?; match res { @@ -867,6 +920,11 @@ where sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events) .await?; } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events) + .await?; + } } Ok(()) diff --git a/wallet/wallet-controller/src/runtime_wallet.rs b/wallet/wallet-controller/src/runtime_wallet.rs index 81dbab57ed..54435898c3 100644 --- a/wallet/wallet-controller/src/runtime_wallet.rs +++ b/wallet/wallet-controller/src/runtime_wallet.rs @@ -64,6 +64,8 @@ use wallet_types::{ Currency, KeyPurpose, KeychainUsageState, SignedTxWithFees, }; +#[cfg(feature = "ledger")] +use wallet::signer::ledger_signer::LedgerSignerProvider; #[cfg(feature = "trezor")] use wallet::signer::trezor_signer::TrezorSignerProvider; @@ -72,6 +74,8 @@ pub enum RuntimeWallet { Software(Wallet), #[cfg(feature = "trezor")] Trezor(Wallet), + #[cfg(feature = "ledger")] + Ledger(Wallet), } impl RuntimeWallet @@ -91,6 +95,10 @@ where RuntimeWallet::Trezor(w) => { w.find_unspent_utxo_and_destination(input, htlc_spending_condition) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.find_unspent_utxo_and_destination(input, htlc_spending_condition) + } } } @@ -99,6 +107,8 @@ where RuntimeWallet::Software(w) => w.find_account_destination(acc_outpoint), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.find_account_destination(acc_outpoint), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.find_account_destination(acc_outpoint), } } @@ -107,6 +117,8 @@ where RuntimeWallet::Software(w) => w.find_account_command_destination(cmd), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.find_account_command_destination(cmd), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.find_account_command_destination(cmd), } } @@ -118,6 +130,8 @@ where RuntimeWallet::Software(w) => w.find_order_account_command_destination(cmd), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.find_order_account_command_destination(cmd), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.find_order_account_command_destination(cmd), } } @@ -126,6 +140,8 @@ where RuntimeWallet::Software(w) => w.seed_phrase(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.seed_phrase(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.seed_phrase(), } } @@ -134,6 +150,8 @@ where RuntimeWallet::Software(w) => w.delete_seed_phrase(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.delete_seed_phrase(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.delete_seed_phrase(), } } @@ -142,6 +160,8 @@ where RuntimeWallet::Software(w) => w.reset_wallet_to_genesis(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.reset_wallet_to_genesis(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.reset_wallet_to_genesis(), } } @@ -150,6 +170,8 @@ where RuntimeWallet::Software(w) => w.encrypt_wallet(password), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.encrypt_wallet(password), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.encrypt_wallet(password), } } @@ -158,6 +180,8 @@ where RuntimeWallet::Software(w) => w.unlock_wallet(password), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.unlock_wallet(password), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.unlock_wallet(password), } } @@ -166,6 +190,8 @@ where RuntimeWallet::Software(w) => w.lock_wallet(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.lock_wallet(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.lock_wallet(), } } @@ -178,6 +204,8 @@ where RuntimeWallet::Software(w) => w.set_lookahead_size(lookahead_size, force_reduce), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.set_lookahead_size(lookahead_size, force_reduce), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.set_lookahead_size(lookahead_size, force_reduce), } } @@ -186,6 +214,8 @@ where RuntimeWallet::Software(w) => w.wallet_info(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.wallet_info(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.wallet_info(), } } @@ -194,6 +224,8 @@ where RuntimeWallet::Software(w) => w.hardware_wallet_info(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.hardware_wallet_info(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.hardware_wallet_info(), } } @@ -205,6 +237,8 @@ where RuntimeWallet::Software(w) => w.create_next_account(name).await, #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.create_next_account(name).await, + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.create_next_account(name).await, } } @@ -217,6 +251,8 @@ where RuntimeWallet::Software(w) => w.set_account_name(account_index, name), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.set_account_name(account_index, name), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.set_account_name(account_index, name), } } @@ -229,6 +265,8 @@ where RuntimeWallet::Software(w) => w.get_pos_gen_block_data(account_index, pool_id), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(_) => Err(WalletError::UnsupportedHardwareWalletOperation), } } @@ -240,6 +278,8 @@ where RuntimeWallet::Software(w) => w.get_pos_gen_block_data_by_pool_id(pool_id), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(_) => Err(WalletError::UnsupportedHardwareWalletOperation), } } @@ -252,6 +292,8 @@ where RuntimeWallet::Software(w) => w.get_pool_ids(account_index, filter), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_pool_ids(account_index, filter), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_pool_ids(account_index, filter), } } @@ -260,6 +302,8 @@ where RuntimeWallet::Software(w) => w.get_best_block(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_best_block(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_best_block(), } } @@ -271,6 +315,8 @@ where RuntimeWallet::Software(w) => w.get_best_block_for_account(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_best_block_for_account(account_index), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_best_block_for_account(account_index), } } @@ -279,6 +325,8 @@ where RuntimeWallet::Software(w) => w.is_locked(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.is_locked(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.is_locked(), } } @@ -297,6 +345,10 @@ where RuntimeWallet::Trezor(w) => { w.get_utxos(account_index, utxo_types, utxo_states, with_locked) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.get_utxos(account_index, utxo_types, utxo_states, with_locked) + } } } @@ -307,6 +359,8 @@ where RuntimeWallet::Software(w) => w.get_transactions_to_be_broadcast(), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_transactions_to_be_broadcast(), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_transactions_to_be_broadcast(), } } @@ -320,6 +374,8 @@ where RuntimeWallet::Software(w) => w.get_balance(account_index, utxo_states, with_locked), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_balance(account_index, utxo_states, with_locked), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_balance(account_index, utxo_states, with_locked), } } @@ -338,6 +394,10 @@ where RuntimeWallet::Trezor(w) => { w.get_multisig_utxos(account_index, utxo_types, utxo_states, with_locked) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.get_multisig_utxos(account_index, utxo_types, utxo_states, with_locked) + } } } @@ -349,6 +409,8 @@ where RuntimeWallet::Software(w) => w.pending_transactions(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.pending_transactions(account_index), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.pending_transactions(account_index), } } @@ -364,6 +426,8 @@ where } #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.mainchain_transactions(account_index, destination, limit), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.mainchain_transactions(account_index, destination, limit), } } @@ -377,6 +441,8 @@ where RuntimeWallet::Software(w) => w.get_transaction_list(account_index, skip, count), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_transaction_list(account_index, skip, count), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_transaction_list(account_index, skip, count), } } @@ -389,6 +455,8 @@ where RuntimeWallet::Software(w) => w.get_transaction(account_index, transaction_id), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_transaction(account_index, transaction_id), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_transaction(account_index, transaction_id), } } @@ -401,6 +469,8 @@ where RuntimeWallet::Software(w) => w.get_all_issued_addresses(account_index, key_purpose), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_all_issued_addresses(account_index, key_purpose), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_all_issued_addresses(account_index, key_purpose), } } @@ -420,6 +490,12 @@ where UtxoState::Confirmed.into(), WithLocked::Unlocked, ), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_address_coin_balances( + account_index, + UtxoState::Confirmed.into(), + WithLocked::Unlocked, + ), } } @@ -431,6 +507,8 @@ where RuntimeWallet::Software(w) => w.get_all_issued_vrf_public_keys(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(_) => Err(WalletError::UnsupportedHardwareWalletOperation), } } @@ -442,6 +520,8 @@ where RuntimeWallet::Software(w) => w.get_legacy_vrf_public_key(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(_) => Err(WalletError::UnsupportedHardwareWalletOperation), } } @@ -454,6 +534,8 @@ where RuntimeWallet::Software(w) => w.get_addresses_usage(account_index, key_purpose), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_addresses_usage(account_index, key_purpose), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_addresses_usage(account_index, key_purpose), } } @@ -465,6 +547,8 @@ where RuntimeWallet::Software(w) => w.get_all_standalone_addresses(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_all_standalone_addresses(account_index), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_all_standalone_addresses(account_index), } } @@ -485,6 +569,10 @@ where RuntimeWallet::Trezor(w) => { w.get_all_standalone_address_details(account_index, address) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.get_all_standalone_address_details(account_index, address) + } } } @@ -496,6 +584,8 @@ where RuntimeWallet::Software(w) => w.get_created_blocks(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_created_blocks(account_index), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_created_blocks(account_index), } } @@ -508,6 +598,8 @@ where RuntimeWallet::Software(w) => w.find_used_tokens(account_index, input_utxos), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.find_used_tokens(account_index, input_utxos), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.find_used_tokens(account_index, input_utxos), } } @@ -520,6 +612,8 @@ where RuntimeWallet::Software(w) => w.get_token_unconfirmed_info(account_index, token_info), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_token_unconfirmed_info(account_index, token_info), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_token_unconfirmed_info(account_index, token_info), } } @@ -532,6 +626,8 @@ where RuntimeWallet::Software(w) => w.abandon_transaction(account_index, tx_id), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.abandon_transaction(account_index, tx_id), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.abandon_transaction(account_index, tx_id), } } @@ -549,6 +645,10 @@ where RuntimeWallet::Trezor(w) => { w.standalone_address_label_rename(account_index, address, label) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.standalone_address_label_rename(account_index, address, label) + } } } @@ -562,6 +662,8 @@ where RuntimeWallet::Software(w) => w.add_standalone_address(account_index, address, label), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.add_standalone_address(account_index, address, label), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.add_standalone_address(account_index, address, label), } } @@ -579,6 +681,10 @@ where RuntimeWallet::Trezor(w) => { w.add_standalone_private_key(account_index, private_key, label) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.add_standalone_private_key(account_index, private_key, label) + } } } @@ -594,6 +700,8 @@ where } #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.add_standalone_multisig(account_index, challenge, label), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.add_standalone_multisig(account_index, challenge, label), } } @@ -605,6 +713,8 @@ where RuntimeWallet::Software(w) => w.get_new_address(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_new_address(account_index), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_new_address(account_index), } } @@ -617,6 +727,8 @@ where RuntimeWallet::Software(w) => w.find_public_key(account_index, address), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.find_public_key(account_index, address), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.find_public_key(account_index, address), } } @@ -628,6 +740,8 @@ where RuntimeWallet::Software(w) => w.account_extended_public_key(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.account_extended_public_key(account_index), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.account_extended_public_key(account_index), } } @@ -639,6 +753,8 @@ where RuntimeWallet::Software(w) => w.get_vrf_key(account_index), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(_) => Err(WalletError::UnsupportedHardwareWalletOperation), } } @@ -669,6 +785,16 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.issue_new_token( + account_index, + token_issuance, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -702,6 +828,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.issue_new_nft( + account_index, + address, + metadata, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -738,6 +875,18 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.mint_tokens( + account_index, + token_info, + amount, + address, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -771,6 +920,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.unmint_tokens( + account_index, + token_info, + amount, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -801,6 +961,16 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.lock_token_supply( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -834,6 +1004,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.freeze_token( + account_index, + token_info, + is_token_unfreezable, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -864,6 +1045,16 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.unfreeze_token( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -897,6 +1088,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.change_token_authority( + account_index, + token_info, + address, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -930,6 +1132,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.change_token_metadata_uri( + account_index, + token_info, + metadata_uri, + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -970,6 +1183,19 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_transaction_to_addresses( + account_index, + outputs, + inputs, + change_addresses, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1003,6 +1229,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_sweep_transaction( + account_index, + destination_address, + filtered_inputs, + current_fee_rate, + additional_info, + ) + .await + } } } @@ -1015,6 +1252,8 @@ where RuntimeWallet::Software(w) => w.get_delegation(account_index, delegation_id), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.get_delegation(account_index, delegation_id), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.get_delegation(account_index, delegation_id), } } @@ -1048,6 +1287,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_sweep_from_delegation_transaction( + account_index, + destination_address, + delegation_id, + delegation_share, + current_fee_rate, + ) + .await + } } } @@ -1085,6 +1335,17 @@ where consolidate_fee_rate, ptx_additional_info, ), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.create_unsigned_transaction_to_addresses( + account_index, + outputs, + selected_inputs, + selection_algo, + change_addresses, + current_fee_rate, + consolidate_fee_rate, + ptx_additional_info, + ), } } @@ -1115,6 +1376,16 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_delegation( + account_index, + vec![output], + current_fee_rate, + consolidate_fee_rate, + ) + .await + } } } @@ -1151,6 +1422,18 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_transaction_to_addresses_from_delegation( + account_index, + address, + amount, + delegation_id, + delegation_share, + current_fee_rate, + ) + .await + } } } @@ -1181,6 +1464,16 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_stake_pool_with_vrf_key( + account_index, + current_fee_rate, + consolidate_fee_rate, + stake_pool_arguments, + ) + .await + } } } @@ -1214,6 +1507,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.decommission_stake_pool( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ) + .await + } } } @@ -1247,6 +1551,17 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.decommission_stake_pool_request( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ) + .await + } } } @@ -1283,6 +1598,18 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_htlc_tx( + account_index, + output_value, + htlc, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1323,6 +1650,19 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_order_tx( + account_index, + ask_value, + give_value, + conclude_key, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1363,6 +1703,19 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_conclude_order_tx( + account_index, + order_id, + order_info, + output_address, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1406,6 +1759,20 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_fill_order_tx( + account_index, + order_id, + order_info, + fill_amount_in_ask_currency, + output_address, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1442,6 +1809,18 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_freeze_order_tx( + account_index, + order_id, + order_info, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1463,6 +1842,10 @@ where RuntimeWallet::Trezor(w) => { w.sign_raw_transaction(account_index, ptx, tokens_additional_info).await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.sign_raw_transaction(account_index, ptx, tokens_additional_info).await + } } } @@ -1480,6 +1863,10 @@ where RuntimeWallet::Trezor(w) => { w.sign_challenge(account_index, challenge, destination).await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.sign_challenge(account_index, challenge, destination).await + } } } @@ -1523,6 +1910,20 @@ where ) .await } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.create_transaction_to_addresses_with_intent( + account_index, + outputs, + inputs, + change_addresses, + intent, + current_fee_rate, + consolidate_fee_rate, + additional_info, + ) + .await + } } } @@ -1535,6 +1936,8 @@ where RuntimeWallet::Software(w) => w.add_unconfirmed_tx(tx, wallet_events), #[cfg(feature = "trezor")] RuntimeWallet::Trezor(w) => w.add_unconfirmed_tx(tx, wallet_events), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w.add_unconfirmed_tx(tx, wallet_events), } } @@ -1552,6 +1955,10 @@ where RuntimeWallet::Trezor(w) => { w.add_account_unconfirmed_tx(account_index, tx.clone(), wallet_events) } + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => { + w.add_account_unconfirmed_tx(account_index, tx.clone(), wallet_events) + } } } @@ -1567,6 +1974,10 @@ where RuntimeWallet::Trezor(w) => w .get_delegations(account_index) .map(|it| -> Box> { Box::new(it) }), + #[cfg(feature = "ledger")] + RuntimeWallet::Ledger(w) => w + .get_delegations(account_index) + .map(|it| -> Box> { Box::new(it) }), } } } diff --git a/wallet/wallet-controller/src/types/mod.rs b/wallet/wallet-controller/src/types/mod.rs index 347b6f9bb8..a46113c5ff 100644 --- a/wallet/wallet-controller/src/types/mod.rs +++ b/wallet/wallet-controller/src/types/mod.rs @@ -196,6 +196,8 @@ pub enum WalletTypeArgs { }, #[cfg(feature = "trezor")] Trezor { device_id: Option }, + #[cfg(feature = "ledger")] + Ledger, } #[derive(Debug, Clone, Copy)] @@ -216,6 +218,8 @@ impl WalletTypeArgs { } => controller_mode.into(), #[cfg(feature = "trezor")] Self::Trezor { device_id: _ } => WalletType::Trezor, + #[cfg(feature = "trezor")] + Self::Ledger => WalletType::Ledger, } } @@ -258,6 +262,11 @@ impl WalletTypeArgs { WalletTypeArgsComputed::Trezor { device_id }, CreatedWallet::UserProvidedMnemonic, )), + #[cfg(feature = "ledger")] + Self::Ledger => Ok(( + WalletTypeArgsComputed::Ledger, + CreatedWallet::UserProvidedMnemonic, + )), } } } @@ -270,6 +279,8 @@ pub enum WalletTypeArgsComputed { }, #[cfg(feature = "trezor")] Trezor { device_id: Option }, + #[cfg(feature = "ledger")] + Ledger, } pub enum SweepFromAddresses { diff --git a/wallet/wallet-node-client/Cargo.toml b/wallet/wallet-node-client/Cargo.toml index 2bf1366b8f..afb2c84780 100644 --- a/wallet/wallet-node-client/Cargo.toml +++ b/wallet/wallet-node-client/Cargo.toml @@ -29,7 +29,13 @@ base64.workspace = true mockall.workspace = true serde_json.workspace = true thiserror.workspace = true -tokio = { workspace = true, default-features = false, features = ["io-util", "macros", "net", "rt", "sync"] } +tokio = { workspace = true, default-features = false, features = [ + "io-util", + "macros", + "net", + "rt", + "sync", +] } tower.workspace = true [dev-dependencies] @@ -37,4 +43,5 @@ chainstate-storage = { path = "../../chainstate/storage" } [features] trezor = ["wallet-types/trezor"] -default = ["trezor"] +ledger = ["wallet-types/ledger"] +default = ["trezor", "ledger"] diff --git a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs index eaca9d165c..57122b7b99 100644 --- a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs +++ b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs @@ -103,6 +103,8 @@ impl WalletInterface for ClientWalletRpc { false, Some(HardwareWalletType::Trezor { device_id }), ), + #[cfg(feature = "ledger")] + WalletTypeArgs::Ledger => (None, None, false, Some(HardwareWalletType::Ledger)), }; ColdWalletRpcClient::create_wallet( @@ -135,6 +137,8 @@ impl WalletInterface for ClientWalletRpc { false, Some(HardwareWalletType::Trezor { device_id }), ), + #[cfg(feature = "ledger")] + WalletTypeArgs::Ledger => (None, None, false, Some(HardwareWalletType::Ledger)), }; ColdWalletRpcClient::recover_wallet( diff --git a/wallet/wallet-rpc-daemon/Cargo.toml b/wallet/wallet-rpc-daemon/Cargo.toml index 23f3126f7d..784b1b9797 100644 --- a/wallet/wallet-rpc-daemon/Cargo.toml +++ b/wallet/wallet-rpc-daemon/Cargo.toml @@ -4,7 +4,11 @@ license.workspace = true edition.workspace = true version.workspace = true rust-version.workspace = true -authors = ["Samer Afach ", "Ben Marsh ", "Enrico Rubboli "] +authors = [ + "Samer Afach ", + "Ben Marsh ", + "Enrico Rubboli ", +] [dependencies] @@ -26,4 +30,5 @@ expect-test.workspace = true [features] trezor = ["wallet-rpc-lib/trezor"] -default = ["trezor"] +ledger = ["wallet-rpc-lib/ledger"] +default = ["trezor", "ledger"] diff --git a/wallet/wallet-rpc-daemon/docs/RPC.md b/wallet/wallet-rpc-daemon/docs/RPC.md index 94461690f3..4da550f13a 100644 --- a/wallet/wallet-rpc-daemon/docs/RPC.md +++ b/wallet/wallet-rpc-daemon/docs/RPC.md @@ -3241,7 +3241,8 @@ Parameters: 1) string 2) null }, } - 2) null, + 2) { "type": "Ledger" } + 3) null, } ``` @@ -3291,7 +3292,8 @@ Parameters: 1) string 2) null }, } - 2) null, + 2) { "type": "Ledger" } + 3) null, } ``` @@ -3338,7 +3340,8 @@ Parameters: 1) string 2) null }, } - 2) null, + 2) { "type": "Ledger" } + 3) null, } ``` diff --git a/wallet/wallet-rpc-lib/src/lib.rs b/wallet/wallet-rpc-lib/src/lib.rs index 74824e1bc3..54ef2bff95 100644 --- a/wallet/wallet-rpc-lib/src/lib.rs +++ b/wallet/wallet-rpc-lib/src/lib.rs @@ -18,7 +18,7 @@ pub mod config; mod rpc; mod service; -#[cfg(feature = "trezor")] +#[cfg(any(feature = "trezor", feature = "ledger"))] use rpc::types::HardwareWalletType; pub use rpc::{ types, ColdWalletRpcClient, ColdWalletRpcDescription, ColdWalletRpcServer, RpcCreds, RpcError, @@ -26,7 +26,7 @@ pub use rpc::{ }; pub use service::{Event, EventStream, TxState, WalletHandle, /* WalletResult, */ WalletService,}; use wallet_controller::{NodeInterface, NodeRpcClient}; -#[cfg(feature = "trezor")] +#[cfg(any(feature = "trezor", feature = "ledger"))] use wallet_types::wallet_type::WalletType; use std::{fmt::Debug, time::Duration}; @@ -108,6 +108,8 @@ where |hw| match hw { #[cfg(feature = "trezor")] HardwareWalletType::Trezor { device_id: _ } => WalletType::Trezor, + #[cfg(feature = "ledger")] + HardwareWalletType::Ledger => WalletType::Ledger, }, ); // Start the wallet service diff --git a/wallet/wallet-rpc-lib/src/rpc/mod.rs b/wallet/wallet-rpc-lib/src/rpc/mod.rs index 7b46022b75..2970df6fe5 100644 --- a/wallet/wallet-rpc-lib/src/rpc/mod.rs +++ b/wallet/wallet-rpc-lib/src/rpc/mod.rs @@ -89,7 +89,7 @@ use wallet_types::{ use crate::{WalletHandle, WalletRpcConfig}; -#[cfg(feature = "trezor")] +#[cfg(any(feature = "trezor", feature = "ledger"))] use wallet_types::wallet_type::WalletType; pub use self::types::RpcError; @@ -160,6 +160,8 @@ where match hw { #[cfg(feature = "trezor")] HardwareWalletType::Trezor { device_id } => (WalletType::Trezor, device_id), + #[cfg(feature = "ledger")] + HardwareWalletType::Ledger => (WalletType::Ledger, None), } }); Ok(self diff --git a/wallet/wallet-rpc-lib/src/rpc/types.rs b/wallet/wallet-rpc-lib/src/rpc/types.rs index 14003c39ac..215a5f1178 100644 --- a/wallet/wallet-rpc-lib/src/rpc/types.rs +++ b/wallet/wallet-rpc-lib/src/rpc/types.rs @@ -1048,6 +1048,8 @@ pub enum RpcCurrency { pub enum HardwareWalletType { #[cfg(feature = "trezor")] Trezor { device_id: Option }, + #[cfg(feature = "ledger")] + Ledger, } impl HardwareWalletType { @@ -1091,6 +1093,8 @@ impl HardwareWalletType { HardwareWalletType::Trezor { device_id } => { Ok(WalletTypeArgs::Trezor { device_id }) } + #[cfg(feature = "ledger")] + HardwareWalletType::Ledger => Ok(WalletTypeArgs::Ledger), } } } diff --git a/wallet/wallet-rpc-lib/tests/utils.rs b/wallet/wallet-rpc-lib/tests/utils.rs index ea55917c93..ea7722cfa2 100644 --- a/wallet/wallet-rpc-lib/tests/utils.rs +++ b/wallet/wallet-rpc-lib/tests/utils.rs @@ -70,14 +70,15 @@ impl TestFramework { db, (BlockHeight::new(0), chain_config.genesis_block_id()), WalletType::Hot, - |db_tx| { - Ok(SoftwareSignerProvider::new_from_mnemonic( + async |db_tx| { + SoftwareSignerProvider::new_from_mnemonic( chain_config.clone(), db_tx, wallet_test_node::MNEMONIC, None, StoreSeedPhrase::DoNotStore, - )?) + ) + .map_err(Into::into) }, ) .await