Skip to main content

quantus_cli/cli/
wallet.rs

1//! `quantus wallet` subcommand - wallet operations
2use crate::{
3	chain::quantus_subxt,
4	cli::address_format::QuantusSS58,
5	error::QuantusError,
6	log_error, log_print, log_success, log_verbose,
7	wallet::{password::get_mnemonic_from_user, WalletManager, DEFAULT_DERIVATION_PATH},
8};
9use clap::Subcommand;
10use colored::Colorize;
11use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
12use std::io::{self, Write};
13
14/// Wallet management commands
15#[derive(Subcommand, Debug)]
16pub enum WalletCommands {
17	/// Create a new wallet with quantum-safe keys
18	Create {
19		/// Wallet name
20		#[arg(short, long)]
21		name: String,
22
23		/// Password to encrypt the wallet (optional, will prompt if not provided)
24		#[arg(short, long)]
25		password: Option<String>,
26
27		/// Derivation path (default: m/44'/189189'/0'/0/0)
28		#[arg(short = 'd', long, default_value = DEFAULT_DERIVATION_PATH)]
29		derivation_path: String,
30
31		/// Disable HD derivation (use master seed directly, like quantus-node --no-derivation)
32		#[arg(long)]
33		no_derivation: bool,
34	},
35
36	/// View wallet information
37	View {
38		/// Wallet name to view
39		#[arg(short, long)]
40		name: Option<String>,
41
42		/// Show all wallets if no name specified
43		#[arg(short, long)]
44		all: bool,
45	},
46
47	/// Export wallet (private key or mnemonic)
48	Export {
49		/// Wallet name to export
50		#[arg(short, long)]
51		name: String,
52
53		/// Password to decrypt the wallet (optional, will prompt if not provided)
54		#[arg(short, long)]
55		password: Option<String>,
56
57		/// Export format: mnemonic, private-key
58		#[arg(short, long, default_value = "mnemonic")]
59		format: String,
60	},
61
62	/// Import wallet from mnemonic phrase
63	Import {
64		/// Wallet name
65		#[arg(short, long)]
66		name: String,
67
68		/// Mnemonic phrase (24 words, will prompt if not provided)
69		#[arg(short, long)]
70		mnemonic: Option<String>,
71
72		/// Password to encrypt the wallet (optional, will prompt if not provided)
73		#[arg(short, long)]
74		password: Option<String>,
75
76		/// Derivation path (default: m/44'/189189'/0'/0/0)
77		#[arg(short = 'd', long, default_value = DEFAULT_DERIVATION_PATH)]
78		derivation_path: String,
79
80		/// Disable HD derivation (use master seed directly, like quantus-node --no-derivation)
81		#[arg(long)]
82		no_derivation: bool,
83	},
84
85	/// Create wallet from 32-byte seed
86	FromSeed {
87		/// Wallet name
88		#[arg(short, long)]
89		name: String,
90
91		/// 32-byte seed in hex format (64 hex characters)
92		#[arg(short, long)]
93		seed: String,
94
95		/// Password to encrypt the wallet (optional, will prompt if not provided)
96		#[arg(short, long)]
97		password: Option<String>,
98	},
99
100	/// List all wallets
101	List,
102
103	/// Delete a wallet
104	Delete {
105		/// Wallet name to delete
106		#[arg(short, long)]
107		name: String,
108
109		/// Skip confirmation prompt
110		#[arg(short, long)]
111		force: bool,
112	},
113
114	/// Get the nonce (transaction count) of an account
115	Nonce {
116		/// Account address to query (optional, uses wallet address if not provided)
117		#[arg(short, long)]
118		address: Option<String>,
119
120		/// Wallet name (used for address if --address not provided)
121		#[arg(short, long, required_unless_present("address"))]
122		wallet: Option<String>,
123
124		/// Password for the wallet
125		#[arg(short, long)]
126		password: Option<String>,
127	},
128}
129
130/// Get the nonce (transaction count) of an account
131pub async fn get_account_nonce(
132	quantus_client: &crate::chain::client::QuantusClient,
133	account_address: &str,
134) -> crate::error::Result<u32> {
135	log_verbose!("#ļøāƒ£ Querying nonce for account: {}", account_address.bright_green());
136
137	// Parse the SS58 address to AccountId32 (sp-core)
138	let (account_id_sp, _) = SpAccountId32::from_ss58check_with_version(account_address)
139		.map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
140
141	log_verbose!("šŸ” SP Account ID: {:?}", account_id_sp);
142
143	// Convert to subxt_core AccountId32 for storage query
144	let account_bytes: [u8; 32] = *account_id_sp.as_ref();
145	let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
146
147	log_verbose!("šŸ” SubXT Account ID: {:?}", account_id);
148
149	// Use SubXT to query System::Account storage directly (like send_subxt.rs)
150	use quantus_subxt::api;
151	let storage_addr = api::storage().system().account(account_id);
152
153	// Get the latest block hash to read from the latest state (not finalized)
154	let latest_block_hash = quantus_client.get_latest_block().await?;
155
156	let storage_at = quantus_client.client().storage().at(latest_block_hash);
157
158	let account_info = storage_at
159		.fetch_or_default(&storage_addr)
160		.await
161		.map_err(|e| QuantusError::NetworkError(format!("Failed to fetch account info: {e:?}")))?;
162
163	log_verbose!("āœ… Account info retrieved with storage query!");
164	log_verbose!("šŸ”¢ Nonce: {}", account_info.nonce);
165
166	Ok(account_info.nonce)
167}
168
169/// Fetch high-security status from chain for an account (SS58). Returns None if disabled or on
170/// error.
171async fn fetch_high_security_status(
172	quantus_client: &crate::chain::client::QuantusClient,
173	account_ss58: &str,
174) -> crate::error::Result<Option<(String, String)>> {
175	use quantus_subxt::api::runtime_types::qp_scheduler::BlockNumberOrTimestamp;
176
177	let (account_id_sp, _) = SpAccountId32::from_ss58check_with_version(account_ss58)
178		.map_err(|e| QuantusError::Generic(format!("Invalid SS58 for HS lookup: {e:?}")))?;
179	let account_bytes: [u8; 32] = *account_id_sp.as_ref();
180	let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
181
182	let storage_addr = quantus_subxt::api::storage()
183		.reversible_transfers()
184		.high_security_accounts(account_id);
185	let latest = quantus_client.get_latest_block().await?;
186	let value = quantus_client
187		.client()
188		.storage()
189		.at(latest)
190		.fetch(&storage_addr)
191		.await
192		.map_err(|e| QuantusError::NetworkError(format!("Fetch HS storage: {e:?}")))?;
193
194	let Some(data) = value else {
195		return Ok(None);
196	};
197
198	let interceptor_ss58 = data.interceptor.to_quantus_ss58();
199	let delay_str = match data.delay {
200		BlockNumberOrTimestamp::BlockNumber(blocks) => format!("{} blocks", blocks),
201		BlockNumberOrTimestamp::Timestamp(ms) => format!("{} seconds", ms / 1000),
202	};
203	Ok(Some((interceptor_ss58, delay_str)))
204}
205
206/// Fetch list of accounts for which this account is guardian (interceptor_index).
207/// Returns an empty vec when the storage entry is absent (`None`), and an error on failure.
208async fn fetch_guardian_for_list(
209	quantus_client: &crate::chain::client::QuantusClient,
210	account_ss58: &str,
211) -> crate::error::Result<Vec<String>> {
212	let account_id_sp = SpAccountId32::from_ss58check(account_ss58)
213		.map_err(|e| QuantusError::Generic(format!("Invalid SS58 for interceptor_index: {e:?}")))?;
214	let account_bytes: [u8; 32] = *account_id_sp.as_ref();
215	let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
216
217	let storage_addr = quantus_subxt::api::storage()
218		.reversible_transfers()
219		.interceptor_index(account_id);
220	let latest = quantus_client.get_latest_block().await?;
221	let value = quantus_client
222		.client()
223		.storage()
224		.at(latest)
225		.fetch(&storage_addr)
226		.await
227		.map_err(|e| QuantusError::NetworkError(format!("Fetch interceptor_index: {e:?}")))?;
228
229	let list = value
230		.map(|bounded| bounded.0.iter().map(|a| a.to_quantus_ss58()).collect())
231		.unwrap_or_default();
232	Ok(list)
233}
234
235/// For each entrusted account (SS58), count pending reversible transfers by sender. Returns (total,
236/// per-account list).
237async fn fetch_pending_transfers_for_guardian(
238	quantus_client: &crate::chain::client::QuantusClient,
239	entrusted_ss58: &[String],
240) -> crate::error::Result<(u32, Vec<(String, u32)>)> {
241	let latest = quantus_client.get_latest_block().await?;
242	let storage = quantus_client.client().storage().at(latest);
243	let mut total = 0u32;
244	let mut per_account = Vec::with_capacity(entrusted_ss58.len());
245
246	for ss58 in entrusted_ss58 {
247		let account_id_sp = SpAccountId32::from_ss58check(ss58).map_err(|e| {
248			QuantusError::Generic(format!("Invalid SS58 for pending lookup: {e:?}"))
249		})?;
250		let account_bytes: [u8; 32] = *account_id_sp.as_ref();
251		let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
252
253		let addr = quantus_subxt::api::storage()
254			.reversible_transfers()
255			.pending_transfers_by_sender(account_id);
256		let value = storage.fetch(&addr).await.map_err(|e| {
257			QuantusError::NetworkError(format!("Fetch pending_transfers_by_sender: {e:?}"))
258		})?;
259
260		let count = value.map(|bounded| bounded.0.len() as u32).unwrap_or(0);
261		total += count;
262		per_account.push((ss58.clone(), count));
263	}
264
265	Ok((total, per_account))
266}
267
268/// Handle wallet commands
269pub async fn handle_wallet_command(
270	command: WalletCommands,
271	node_url: &str,
272) -> crate::error::Result<()> {
273	match command {
274		WalletCommands::Create { name, password, derivation_path, no_derivation } => {
275			log_print!("šŸ” Creating new quantum wallet...");
276
277			let wallet_manager = WalletManager::new()?;
278
279			// Choose creation method based on flags
280			let result = if no_derivation {
281				// Use master seed directly (like quantus-node --no-derivation)
282				wallet_manager.create_wallet_no_derivation(&name, password.as_deref()).await
283			} else if derivation_path == DEFAULT_DERIVATION_PATH {
284				wallet_manager.create_wallet(&name, password.as_deref()).await
285			} else {
286				wallet_manager
287					.create_wallet_with_derivation_path(
288						&name,
289						password.as_deref(),
290						&derivation_path,
291					)
292					.await
293			};
294
295			match result {
296				Ok(wallet_info) => {
297					log_success!("Wallet name: {}", name.bright_green());
298					log_success!("Address: {}", wallet_info.address.bright_cyan());
299					log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
300					log_success!(
301						"Derivation path: {}",
302						wallet_info.derivation_path.bright_magenta()
303					);
304					log_success!(
305						"Created: {}",
306						wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
307					);
308					log_success!("āœ… Wallet created successfully!");
309				},
310				Err(e) => {
311					log_error!("{}", format!("āŒ Failed to create wallet: {e}").red());
312					return Err(e);
313				},
314			}
315
316			Ok(())
317		},
318
319		WalletCommands::View { name, all } => {
320			log_print!("šŸ‘ļø  Viewing wallet information...");
321
322			let wallet_manager = WalletManager::new()?;
323
324			if all {
325				// Show all wallets (same as list command but with different header)
326				match wallet_manager.list_wallets() {
327					Ok(wallets) =>
328						if wallets.is_empty() {
329							log_print!("{}", "No wallets found.".dimmed());
330						} else {
331							log_print!("All wallets ({}):\n", wallets.len());
332
333							for (i, wallet) in wallets.iter().enumerate() {
334								log_print!(
335									"{}. {}",
336									(i + 1).to_string().bright_yellow(),
337									wallet.name.bright_green()
338								);
339								log_print!("   Address: {}", wallet.address.bright_cyan());
340								log_print!("   Type: {}", wallet.key_type.bright_yellow());
341								log_print!(
342									"   Derivation Path: {}",
343									wallet.derivation_path.bright_magenta()
344								);
345								log_print!(
346									"   Created: {}",
347									wallet
348										.created_at
349										.format("%Y-%m-%d %H:%M:%S UTC")
350										.to_string()
351										.dimmed()
352								);
353								if i < wallets.len() - 1 {
354									log_print!();
355								}
356							}
357						},
358					Err(e) => {
359						log_error!("{}", format!("āŒ Failed to view wallets: {e}").red());
360						return Err(e);
361					},
362				}
363			} else if let Some(wallet_name) = name {
364				// Show specific wallet details
365				match wallet_manager.get_wallet(&wallet_name, None) {
366					Ok(Some(wallet_info)) => {
367						log_print!("Wallet Details:\n");
368						log_print!("Name: {}", wallet_info.name.bright_green());
369						log_print!("Address: {}", wallet_info.address.bright_cyan());
370						log_print!("Key Type: {}", wallet_info.key_type.bright_yellow());
371						log_print!(
372							"Derivation Path: {}",
373							wallet_info.derivation_path.bright_magenta()
374						);
375						log_print!(
376							"Created: {}",
377							wallet_info
378								.created_at
379								.format("%Y-%m-%d %H:%M:%S UTC")
380								.to_string()
381								.dimmed()
382						);
383
384						if wallet_info.address.contains("[") {
385							log_print!(
386								"\n{}",
387								"šŸ’” To see the full address, use the export command with password"
388									.dimmed()
389							);
390						}
391
392						// High-Security status and Guardian-for list from chain (optional; don't
393						// fail view if node unavailable)
394						if !wallet_info.address.contains("[") {
395							if let Ok(quantus_client) =
396								crate::chain::client::QuantusClient::new(node_url).await
397							{
398								match fetch_high_security_status(
399									&quantus_client,
400									&wallet_info.address,
401								)
402								.await
403								{
404									Ok(Some((interceptor_ss58, delay_str))) => {
405										log_print!(
406											"\nšŸ›”ļø  High Security: {}",
407											"ENABLED".bright_green().bold()
408										);
409										log_print!(
410											"   Guardian/Interceptor: {}",
411											interceptor_ss58.bright_cyan()
412										);
413										log_print!("   Delay: {}", delay_str.bright_yellow());
414									},
415									Ok(None) => {
416										log_print!("\nšŸ›”ļø  High Security: {}", "DISABLED".dimmed());
417									},
418									Err(e) => {
419										log_verbose!("High Security status skipped: {}", e);
420										log_print!(
421											"\n{}",
422											"šŸ’” Run quantus high-security status --account <address> to check on-chain"
423												.dimmed()
424										);
425									},
426								}
427
428								// Guardian for: accounts that have this wallet as their interceptor
429								if let Ok(entrusted) =
430									fetch_guardian_for_list(&quantus_client, &wallet_info.address)
431										.await
432								{
433									if entrusted.is_empty() {
434										log_print!("šŸ›”ļø  Guardian for: {}", "none".dimmed());
435									} else {
436										log_print!(
437											"\nšŸ›”ļø  Guardian for: {} account(s)",
438											entrusted.len().to_string().bright_green()
439										);
440										for (i, addr) in entrusted.iter().enumerate() {
441											log_print!("   {}. {}", i + 1, addr.bright_cyan());
442										}
443										// Pending reversible transfers that this guardian can
444										// intercept
445										if let Ok((total, per_account)) =
446											fetch_pending_transfers_for_guardian(
447												&quantus_client,
448												&entrusted,
449											)
450											.await
451										{
452											if total > 0 {
453												log_print!(
454													"\n   {} {} pending transfer(s) you can intercept",
455													"āš ļø".bright_yellow(),
456													total.to_string().bright_yellow().bold()
457												);
458												for (addr, count) in per_account {
459													if count > 0 {
460														log_print!(
461															"      from {}: {}",
462															addr.bright_cyan(),
463															count
464														);
465													}
466												}
467												log_print!("   {}", "Use: quantus reversible cancel --tx-id <id> --from <you>".dimmed());
468											}
469										}
470									}
471								}
472							} else {
473								log_verbose!(
474									"Could not connect to node; High Security status skipped."
475								);
476							}
477						}
478					},
479					Ok(None) => {
480						log_error!("{}", format!("āŒ Wallet '{wallet_name}' not found").red());
481						log_print!(
482							"Use {} to see available wallets",
483							"quantus wallet list".bright_green()
484						);
485					},
486					Err(e) => {
487						log_error!("{}", format!("āŒ Failed to view wallet: {e}").red());
488						return Err(e);
489					},
490				}
491			} else {
492				log_print!(
493					"{}",
494					"Please specify a wallet name with --name or use --all to show all wallets"
495						.yellow()
496				);
497				log_print!("Examples:");
498				log_print!("  {}", "quantus wallet view --name my-wallet".bright_green());
499				log_print!("  {}", "quantus wallet view --all".bright_green());
500			}
501
502			Ok(())
503		},
504
505		WalletCommands::Export { name, password, format } => {
506			log_print!("šŸ“¤ Exporting wallet...");
507
508			if format.to_lowercase() != "mnemonic" {
509				log_error!("Only 'mnemonic' export format is currently supported.");
510				return Err(crate::error::QuantusError::Generic(
511					"Export format not supported".to_string(),
512				));
513			}
514
515			let wallet_manager = WalletManager::new()?;
516
517			match wallet_manager.export_mnemonic(&name, password.as_deref()) {
518				Ok(mnemonic) => {
519					log_success!("āœ… Wallet exported successfully!");
520					log_print!("\nYour secret mnemonic phrase:");
521					log_print!("{}", "--------------------------------------------------".dimmed());
522					log_print!("{}", mnemonic.bright_yellow());
523					log_print!("{}", "--------------------------------------------------".dimmed());
524					log_print!(
525                        "\n{}",
526                        "āš ļø  Keep this phrase safe and secret. Anyone with this phrase can access your funds."
527                            .bright_red()
528                    );
529				},
530				Err(e) => {
531					log_error!("{}", format!("āŒ Failed to export wallet: {e}").red());
532					return Err(e);
533				},
534			}
535
536			Ok(())
537		},
538
539		WalletCommands::Import { name, mnemonic, password, derivation_path, no_derivation } => {
540			log_print!("šŸ“„ Importing wallet...");
541
542			let wallet_manager = WalletManager::new()?;
543
544			// Get mnemonic from user if not provided
545			let mnemonic_phrase =
546				if let Some(mnemonic) = mnemonic { mnemonic } else { get_mnemonic_from_user()? };
547
548			// Get password from user if not provided
549			let final_password =
550				crate::wallet::password::get_wallet_password(&name, password, None)?;
551
552			// Choose import method based on flags
553			let result = if no_derivation {
554				// Use master seed directly (like quantus-node --no-derivation)
555				wallet_manager
556					.import_wallet_no_derivation(&name, &mnemonic_phrase, Some(&final_password))
557					.await
558			} else if derivation_path == DEFAULT_DERIVATION_PATH {
559				wallet_manager
560					.import_wallet(&name, &mnemonic_phrase, Some(&final_password))
561					.await
562			} else {
563				wallet_manager
564					.import_wallet_with_derivation_path(
565						&name,
566						&mnemonic_phrase,
567						Some(&final_password),
568						&derivation_path,
569					)
570					.await
571			};
572
573			match result {
574				Ok(wallet_info) => {
575					log_success!("Wallet name: {}", name.bright_green());
576					log_success!("Address: {}", wallet_info.address.bright_cyan());
577					log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
578					log_success!(
579						"Derivation path: {}",
580						wallet_info.derivation_path.bright_magenta()
581					);
582					log_success!(
583						"Imported: {}",
584						wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
585					);
586					log_success!("āœ… Wallet imported successfully!");
587				},
588				Err(e) => {
589					log_error!("{}", format!("āŒ Failed to import wallet: {e}").red());
590					return Err(e);
591				},
592			}
593
594			Ok(())
595		},
596
597		WalletCommands::FromSeed { name, seed, password } => {
598			log_print!("🌱 Creating wallet from seed...");
599
600			let wallet_manager = WalletManager::new()?;
601
602			// Get password from user if not provided
603			let final_password =
604				crate::wallet::password::get_wallet_password(&name, password, None)?;
605
606			match wallet_manager
607				.create_wallet_from_seed(&name, &seed, Some(&final_password))
608				.await
609			{
610				Ok(wallet_info) => {
611					log_success!("Wallet name: {}", name.bright_green());
612					log_success!("Address: {}", wallet_info.address.bright_cyan());
613					log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
614					log_success!(
615						"Created: {}",
616						wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
617					);
618					log_success!("āœ… Wallet created from seed successfully!");
619				},
620				Err(e) => {
621					log_error!("{}", format!("āŒ Failed to create wallet from seed: {e}").red());
622					return Err(e);
623				},
624			}
625
626			Ok(())
627		},
628
629		WalletCommands::List => {
630			log_print!("šŸ“‹ Listing all wallets...");
631
632			let wallet_manager = WalletManager::new()?;
633
634			match wallet_manager.list_wallets() {
635				Ok(wallets) =>
636					if wallets.is_empty() {
637						log_print!("{}", "No wallets found.".dimmed());
638						log_print!(
639							"Create a new wallet with: {}",
640							"quantus wallet create --name <name>".bright_green()
641						);
642					} else {
643						log_print!("Found {} wallet(s):\n", wallets.len());
644
645						for (i, wallet) in wallets.iter().enumerate() {
646							log_print!(
647								"{}. {}",
648								(i + 1).to_string().bright_yellow(),
649								wallet.name.bright_green()
650							);
651							log_print!("   Address: {}", wallet.address.bright_cyan());
652							log_print!("   Type: {}", wallet.key_type.bright_yellow());
653							log_print!(
654								"   Created: {}",
655								wallet
656									.created_at
657									.format("%Y-%m-%d %H:%M:%S UTC")
658									.to_string()
659									.dimmed()
660							);
661							if i < wallets.len() - 1 {
662								log_print!();
663							}
664						}
665
666						log_print!(
667							"\n{}",
668							"šŸ’” Use 'quantus wallet view --name <wallet>' to see full details"
669								.dimmed()
670						);
671					},
672				Err(e) => {
673					log_error!("{}", format!("āŒ Failed to list wallets: {e}").red());
674					return Err(e);
675				},
676			}
677
678			Ok(())
679		},
680
681		WalletCommands::Delete { name, force } => {
682			log_print!("šŸ—‘ļø  Deleting wallet...");
683
684			let wallet_manager = WalletManager::new()?;
685
686			// Check if wallet exists first
687			match wallet_manager.get_wallet(&name, None) {
688				Ok(Some(wallet_info)) => {
689					// Show wallet info before deletion
690					log_print!("Wallet to delete:");
691					log_print!("  Name: {}", wallet_info.name.bright_green());
692					log_print!("  Address: {}", wallet_info.address.bright_cyan());
693					log_print!("  Type: {}", wallet_info.key_type.bright_yellow());
694					log_print!(
695						"  Created: {}",
696						wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
697					);
698
699					// Confirmation prompt unless --force is used
700					if !force {
701						log_print!("\n{}", "āš ļø  This action cannot be undone!".bright_red());
702						log_print!("Type the wallet name to confirm deletion:");
703
704						print!("Confirm wallet name: ");
705						io::stdout().flush().unwrap();
706
707						let mut input = String::new();
708						io::stdin().read_line(&mut input).unwrap();
709						let input = input.trim();
710
711						if input != name {
712							log_print!(
713								"{}",
714								"āŒ Wallet name doesn't match. Deletion cancelled.".red()
715							);
716							return Ok(());
717						}
718					}
719
720					// Perform deletion
721					match wallet_manager.delete_wallet(&name) {
722						Ok(true) => {
723							log_success!("āœ… Wallet '{}' deleted successfully!", name);
724						},
725						Ok(false) => {
726							log_error!("{}", format!("āŒ Wallet '{name}' was not found").red());
727						},
728						Err(e) => {
729							log_error!("{}", format!("āŒ Failed to delete wallet: {e}").red());
730							return Err(e);
731						},
732					}
733				},
734				Ok(None) => {
735					log_error!("{}", format!("āŒ Wallet '{name}' not found").red());
736					log_print!(
737						"Use {} to see available wallets",
738						"quantus wallet list".bright_green()
739					);
740				},
741				Err(e) => {
742					log_error!("{}", format!("āŒ Failed to check wallet: {e}").red());
743					return Err(e);
744				},
745			}
746
747			Ok(())
748		},
749
750		WalletCommands::Nonce { address, wallet, password } => {
751			log_print!("šŸ”¢ Querying account nonce...");
752
753			let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
754
755			// Determine which address to query
756			let target_address = match (address, wallet) {
757				(Some(addr), _) => {
758					// Validate the provided address
759					SpAccountId32::from_ss58check(&addr)
760						.map_err(|e| QuantusError::Generic(format!("Invalid address: {e:?}")))?;
761					addr
762				},
763				(None, Some(wallet_name)) => {
764					// Load wallet and get its address
765					let keypair =
766						crate::wallet::load_keypair_from_wallet(&wallet_name, password, None)?;
767					keypair.to_account_id_ss58check()
768				},
769				(None, None) => {
770					// This case should be prevented by clap's `required_unless_present`
771					unreachable!("Either --address or --wallet must be provided");
772				},
773			};
774
775			log_print!("Account: {}", target_address.bright_cyan());
776
777			match get_account_nonce(&quantus_client, &target_address).await {
778				Ok(nonce) => {
779					log_success!("Nonce: {}", nonce.to_string().bright_green());
780				},
781				Err(e) => {
782					log_print!("āŒ Failed to get nonce: {}", e);
783					return Err(e);
784				},
785			}
786
787			Ok(())
788		},
789	}
790}