Skip to content

Commit b66c972

Browse files
committed
Add ledger support in CLI and GUI wallet
1 parent 4c711b6 commit b66c972

29 files changed

Lines changed: 879 additions & 131 deletions

File tree

node-gui/backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ ledger = [
6565
"wallet-rpc-client/ledger",
6666
"wallet-cli-commands/ledger",
6767
]
68+
default = ["trezor", "ledger"]

node-gui/backend/src/backend_impl.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,43 @@ impl Backend {
354354

355355
(wallet_data, accounts_info, best_block)
356356
}
357+
#[cfg(feature = "ledger")]
358+
(WalletType::Ledger, ColdHotNodeController::Hot(controller)) => {
359+
let handles_client = WalletHandlesClient::new(
360+
controller.chainstate.clone(),
361+
controller.mempool.clone(),
362+
controller.block_prod.clone(),
363+
controller.p2p.clone(),
364+
)
365+
.await
366+
.map_err(|e| BackendError::WalletError(e.to_string()))?;
367+
368+
let (wallet_rpc, command_handler, best_block, accounts_info, accounts_data) = self
369+
.create_wallet(
370+
handles_client,
371+
file_path.clone(),
372+
wallet_args,
373+
import,
374+
wallet_events,
375+
)
376+
.await?;
377+
378+
let wallet_data = WalletData {
379+
controller: GuiHotColdController::Hot(wallet_rpc, command_handler),
380+
accounts: accounts_data,
381+
best_block,
382+
updated: false,
383+
};
384+
385+
(wallet_data, accounts_info, best_block)
386+
}
357387
#[cfg(feature = "trezor")]
358388
(WalletType::Trezor, ColdHotNodeController::Cold) => {
359-
return Err(BackendError::ColdTrezorNotSupported)
389+
return Err(BackendError::HardwareWalletNotSupportedInColdMode)
390+
}
391+
#[cfg(feature = "ledger")]
392+
(WalletType::Ledger, ColdHotNodeController::Cold) => {
393+
return Err(BackendError::HardwareWalletNotSupportedInColdMode)
360394
}
361395
(WalletType::Hot, ColdHotNodeController::Cold) => {
362396
return Err(BackendError::HotNotSupported)
@@ -593,9 +627,49 @@ impl Backend {
593627

594628
(wallet_data, accounts_info, best_block, encryption_state)
595629
}
630+
#[cfg(feature = "ledger")]
631+
(WalletType::Ledger, ColdHotNodeController::Hot(controller)) => {
632+
let handles_client = WalletHandlesClient::new(
633+
controller.chainstate.clone(),
634+
controller.mempool.clone(),
635+
controller.block_prod.clone(),
636+
controller.p2p.clone(),
637+
)
638+
.await
639+
.map_err(|e| BackendError::WalletError(e.to_string()))?;
640+
641+
let (
642+
wallet_rpc,
643+
command_handler,
644+
encryption_state,
645+
best_block,
646+
accounts_info,
647+
accounts_data,
648+
) = self
649+
.open_wallet(
650+
handles_client,
651+
file_path.clone(),
652+
wallet_events,
653+
Some(HardwareWalletType::Ledger),
654+
)
655+
.await?;
656+
657+
let wallet_data = WalletData {
658+
controller: GuiHotColdController::Hot(wallet_rpc, command_handler),
659+
accounts: accounts_data,
660+
best_block,
661+
updated: false,
662+
};
663+
664+
(wallet_data, accounts_info, best_block, encryption_state)
665+
}
596666
#[cfg(feature = "trezor")]
597667
(WalletType::Trezor, ColdHotNodeController::Cold) => {
598-
return Err(BackendError::ColdTrezorNotSupported)
668+
return Err(BackendError::HardwareWalletNotSupportedInColdMode)
669+
}
670+
#[cfg(feature = "ledger")]
671+
(WalletType::Ledger, ColdHotNodeController::Cold) => {
672+
return Err(BackendError::HardwareWalletNotSupportedInColdMode)
599673
}
600674
(WalletType::Hot, ColdHotNodeController::Cold) => {
601675
return Err(BackendError::HotNotSupported)

node-gui/backend/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ pub enum BackendError {
4646
ColdWallet,
4747
#[error("Cannot interact with a hot wallet when in Cold wallet mode")]
4848
HotNotSupported,
49-
#[error("Cannot use a Trezor wallet in a Cold wallet mode")]
50-
ColdTrezorNotSupported,
49+
#[error("Cannot use a Hardware wallet in a Cold wallet mode")]
50+
HardwareWalletNotSupportedInColdMode,
5151
#[error("Invalid console command: {0}")]
5252
InvalidConsoleCommand(String),
5353
#[error("Empty console command")]

node-gui/src/main_window/main_menu.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,27 @@ fn make_menu_file<'a>(wallet_mode: WalletMode) -> Item<'a, MenuMessage, Theme, i
125125
},
126126
),
127127
];
128+
#[cfg(feature = "ledger")]
129+
{
130+
menu.push(menu_item(
131+
"(Beta) Create new Ledger wallet",
132+
MenuMessage::CreateNewWallet {
133+
wallet_type: WalletType::Ledger,
134+
},
135+
));
136+
menu.push(menu_item(
137+
"(Beta) Recover from Ledger wallet",
138+
MenuMessage::RecoverWallet {
139+
wallet_type: WalletType::Ledger,
140+
},
141+
));
142+
menu.push(menu_item(
143+
"(Beta) Open Ledger wallet",
144+
MenuMessage::OpenWallet {
145+
wallet_type: WalletType::Ledger,
146+
},
147+
));
148+
}
128149
#[cfg(feature = "trezor")]
129150
{
130151
menu.push(menu_item(
@@ -146,6 +167,7 @@ fn make_menu_file<'a>(wallet_mode: WalletMode) -> Item<'a, MenuMessage, Theme, i
146167
},
147168
));
148169
}
170+
149171
// TODO: enable setting when needed
150172
// menu.push(menu_item("Settings", MenuMessage::NoOp));
151173
menu.push(menu_item("Exit", MenuMessage::Exit));

node-gui/src/main_window/main_widget/tabs/wallet/left_panel.rs

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,48 @@ pub fn view_left_panel(
118118
.on_press(WalletMessage::SelectPanel(panel))
119119
.padding(panel_button_row_padding)
120120
};
121+
let is_cold_wallet = wallet_info.wallet_type == WalletType::Cold;
121122

122123
// `next_height` is used to prevent flickering when a new block is found
123-
let show_scan_progress = match wallet_info.wallet_type {
124-
WalletType::Cold => false,
125-
#[cfg(feature = "trezor")]
126-
WalletType::Trezor => {
127-
wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height
128-
}
129-
WalletType::Hot => {
130-
wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height
131-
}
124+
let show_scan_progress = if is_cold_wallet {
125+
false
126+
} else {
127+
wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height
128+
};
129+
130+
let hardware_wallet_panels = || {
131+
column![
132+
panel_button(
133+
"Transactions",
134+
SelectedPanel::Transactions,
135+
selected_panel,
136+
TRANSACTIONS_TOOLTIP_TEXT
137+
),
138+
panel_button(
139+
"Addresses",
140+
SelectedPanel::Addresses,
141+
selected_panel,
142+
ADDRESSES_TOOLTIP_TEXT
143+
),
144+
panel_button(
145+
"Send",
146+
SelectedPanel::Send,
147+
selected_panel,
148+
SEND_TOOLTIP_TEXT
149+
),
150+
panel_button(
151+
"Delegation",
152+
SelectedPanel::Delegation,
153+
selected_panel,
154+
DELEGATION_TOOLTIP_TEXT
155+
),
156+
panel_button(
157+
"Console",
158+
SelectedPanel::Console,
159+
selected_panel,
160+
CONSOLE_TOOLTIP_TEXT,
161+
)
162+
]
132163
};
133164

134165
let scan_progress_widget = if show_scan_progress {
@@ -188,38 +219,11 @@ pub fn view_left_panel(
188219
match wallet_info.wallet_type {
189220
#[cfg(feature = "trezor")]
190221
WalletType::Trezor => {
191-
column![
192-
panel_button(
193-
"Transactions",
194-
SelectedPanel::Transactions,
195-
selected_panel,
196-
TRANSACTIONS_TOOLTIP_TEXT
197-
),
198-
panel_button(
199-
"Addresses",
200-
SelectedPanel::Addresses,
201-
selected_panel,
202-
ADDRESSES_TOOLTIP_TEXT
203-
),
204-
panel_button(
205-
"Send",
206-
SelectedPanel::Send,
207-
selected_panel,
208-
SEND_TOOLTIP_TEXT
209-
),
210-
panel_button(
211-
"Delegation",
212-
SelectedPanel::Delegation,
213-
selected_panel,
214-
DELEGATION_TOOLTIP_TEXT
215-
),
216-
panel_button(
217-
"Console",
218-
SelectedPanel::Console,
219-
selected_panel,
220-
CONSOLE_TOOLTIP_TEXT,
221-
)
222-
]
222+
hardware_wallet_panels()
223+
}
224+
#[cfg(feature = "ledger")]
225+
WalletType::Ledger => {
226+
hardware_wallet_panels()
223227
}
224228
WalletType::Cold => {
225229
column![

node-gui/src/main_window/main_widget/tabs/wallet/mod.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ impl WalletTab {
183183
WalletType::Cold => SelectedPanel::Addresses,
184184
#[cfg(feature = "trezor")]
185185
WalletType::Trezor => SelectedPanel::Transactions,
186+
#[cfg(feature = "ledger")]
187+
WalletType::Ledger => SelectedPanel::Transactions,
186188
};
187189

188190
WalletTab {
@@ -490,15 +492,12 @@ impl Tab for WalletTab {
490492
.get(&self.selected_account)
491493
.expect("selected account must be known");
492494

493-
let still_syncing = match wallet_info.wallet_type {
494-
WalletType::Cold => false,
495-
#[cfg(feature = "trezor")]
496-
WalletType::Trezor => {
497-
wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height
498-
}
499-
WalletType::Hot => {
500-
wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height
501-
}
495+
let is_cold_wallet = wallet_info.wallet_type == WalletType::Cold;
496+
497+
let still_syncing = if is_cold_wallet {
498+
false
499+
} else {
500+
wallet_info.best_block.1.next_height() < node_state.chain_info.best_block_height
502501
}
503502
.then_some(WalletMessage::StillSyncing);
504503

node-gui/src/main_window/mod.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use wallet_cli_commands::ConsoleCommand;
3838
use wallet_controller::types::WalletTypeArgs;
3939
use wallet_types::{seed_phrase::StoreSeedPhrase, wallet_type::WalletType, ImportOrCreate};
4040

41-
#[cfg(feature = "trezor")]
41+
#[cfg(any(feature = "trezor", feature = "ledger"))]
4242
use crate::widgets::create_hw_wallet::hw_wallet_create_dialog;
4343
use crate::{
4444
main_window::{main_menu::MenuMessage, main_widget::MainWidgetMessage},
@@ -148,6 +148,8 @@ pub enum WalletArgs {
148148
},
149149
#[cfg(feature = "trezor")]
150150
Trezor,
151+
#[cfg(feature = "ledger")]
152+
Ledger,
151153
}
152154

153155
impl From<&WalletArgs> for WalletType {
@@ -165,6 +167,8 @@ impl From<&WalletArgs> for WalletType {
165167
}
166168
#[cfg(feature = "trezor")]
167169
WalletArgs::Trezor => WalletType::Trezor,
170+
#[cfg(feature = "ledger")]
171+
WalletArgs::Ledger => WalletType::Ledger,
168172
}
169173
}
170174
}
@@ -291,6 +295,8 @@ impl MainWindow {
291295
},
292296
#[cfg(feature = "trezor")]
293297
WalletType::Trezor => WalletArgs::Trezor,
298+
#[cfg(feature = "ledger")]
299+
WalletType::Ledger => WalletArgs::Ledger,
294300
};
295301
self.active_dialog = ActiveDialog::WalletCreate { wallet_args };
296302
Task::none()
@@ -733,6 +739,8 @@ impl MainWindow {
733739
}
734740
#[cfg(feature = "trezor")]
735741
WalletArgs::Trezor => WalletTypeArgs::Trezor { device_id: None },
742+
#[cfg(feature = "ledger")]
743+
WalletArgs::Ledger => WalletTypeArgs::Ledger,
736744
};
737745

738746
self.file_dialog_active = true;
@@ -864,6 +872,16 @@ impl MainWindow {
864872
ImportOrCreate::Create,
865873
)
866874
.into(),
875+
#[cfg(feature = "ledger")]
876+
WalletArgs::Ledger => hw_wallet_create_dialog(
877+
Box::new(move || MainWindowMessage::ImportWalletMnemonic {
878+
args: WalletArgs::Ledger,
879+
import: ImportOrCreate::Create,
880+
}),
881+
Box::new(|| MainWindowMessage::CloseDialog),
882+
ImportOrCreate::Create,
883+
)
884+
.into(),
867885
},
868886
ActiveDialog::WalletRecover { wallet_type } => {
869887
let is_cold = *wallet_type == WalletType::Cold;
@@ -888,6 +906,16 @@ impl MainWindow {
888906
ImportOrCreate::Import,
889907
)
890908
.into(),
909+
#[cfg(feature = "ledger")]
910+
WalletType::Ledger => hw_wallet_create_dialog(
911+
Box::new(move || MainWindowMessage::ImportWalletMnemonic {
912+
args: WalletArgs::Ledger,
913+
import: ImportOrCreate::Create,
914+
}),
915+
Box::new(|| MainWindowMessage::CloseDialog),
916+
ImportOrCreate::Import,
917+
)
918+
.into(),
891919
}
892920
}
893921

wallet/src/key_chain/master_key_chain/mod.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use crypto::key::hdkd::u31::U31;
2121
use crypto::vrf::ExtendedVRFPrivateKey;
2222
use std::sync::Arc;
2323
use wallet_storage::{
24-
StoreTxRwUnlocked, WalletStorageReadLocked, WalletStorageReadUnlocked,
25-
WalletStorageWriteLocked, WalletStorageWriteUnlocked,
24+
WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteLocked,
25+
WalletStorageWriteUnlocked,
2626
};
2727
use wallet_types::seed_phrase::{SerializableSeedPhrase, StoreSeedPhrase};
2828

@@ -59,9 +59,9 @@ impl MasterKeyChain {
5959
))
6060
}
6161

62-
pub fn new_from_mnemonic<B: storage::Backend>(
62+
pub fn new_from_mnemonic(
6363
chain_config: Arc<ChainConfig>,
64-
db_tx: &mut StoreTxRwUnlocked<B>,
64+
db_tx: &mut impl WalletStorageWriteUnlocked,
6565
mnemonic_str: &str,
6666
passphrase: Option<&str>,
6767
save_seed_phrase: StoreSeedPhrase,
@@ -80,9 +80,9 @@ impl MasterKeyChain {
8080
)
8181
}
8282

83-
fn new_from_root_key<B: storage::Backend>(
83+
fn new_from_root_key(
8484
chain_config: Arc<ChainConfig>,
85-
db_tx: &mut StoreTxRwUnlocked<B>,
85+
db_tx: &mut impl WalletStorageWriteUnlocked,
8686
root_key: ExtendedPrivateKey,
8787
root_vrf_key: ExtendedVRFPrivateKey,
8888
seed_phrase: Option<SerializableSeedPhrase>,

0 commit comments

Comments
 (0)