1use crate::{
3 chain::quantus_subxt,
4 error::QuantusError,
5 log_error, log_print, log_success, log_verbose,
6 wallet::{password::get_mnemonic_from_user, WalletManager, DEFAULT_DERIVATION_PATH},
7};
8use clap::Subcommand;
9use colored::Colorize;
10use sp_core::crypto::{AccountId32, Ss58Codec};
11use std::io::{self, Write};
12
13#[derive(Subcommand, Debug)]
15pub enum WalletCommands {
16 Create {
18 #[arg(short, long)]
20 name: String,
21
22 #[arg(short, long)]
24 password: Option<String>,
25
26 #[arg(short = 'd', long, default_value = DEFAULT_DERIVATION_PATH)]
28 derivation_path: String,
29
30 #[arg(long)]
32 no_derivation: bool,
33 },
34
35 View {
37 #[arg(short, long)]
39 name: Option<String>,
40
41 #[arg(short, long)]
43 all: bool,
44 },
45
46 Export {
48 #[arg(short, long)]
50 name: String,
51
52 #[arg(short, long)]
54 password: Option<String>,
55
56 #[arg(short, long, default_value = "mnemonic")]
58 format: String,
59 },
60
61 Import {
63 #[arg(short, long)]
65 name: String,
66
67 #[arg(short, long)]
69 mnemonic: Option<String>,
70
71 #[arg(short, long)]
73 password: Option<String>,
74
75 #[arg(short = 'd', long, default_value = DEFAULT_DERIVATION_PATH)]
77 derivation_path: String,
78
79 #[arg(long)]
81 no_derivation: bool,
82 },
83
84 FromSeed {
86 #[arg(short, long)]
88 name: String,
89
90 #[arg(short, long)]
92 seed: String,
93
94 #[arg(short, long)]
96 password: Option<String>,
97 },
98
99 List,
101
102 Delete {
104 #[arg(short, long)]
106 name: String,
107
108 #[arg(short, long)]
110 force: bool,
111 },
112
113 Nonce {
115 #[arg(short, long)]
117 address: Option<String>,
118
119 #[arg(short, long, required_unless_present("address"))]
121 wallet: Option<String>,
122
123 #[arg(short, long)]
125 password: Option<String>,
126 },
127}
128
129pub async fn get_account_nonce(
131 quantus_client: &crate::chain::client::QuantusClient,
132 account_address: &str,
133) -> crate::error::Result<u32> {
134 log_verbose!("#️⃣ Querying nonce for account: {}", account_address.bright_green());
135
136 let (account_id_sp, _) = AccountId32::from_ss58check_with_version(account_address)
138 .map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
139
140 log_verbose!("🔍 SP Account ID: {:?}", account_id_sp);
141
142 let account_bytes: [u8; 32] = *account_id_sp.as_ref();
144 let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
145
146 log_verbose!("🔍 SubXT Account ID: {:?}", account_id);
147
148 use quantus_subxt::api;
150 let storage_addr = api::storage().system().account(account_id);
151
152 let latest_block_hash = quantus_client.get_latest_block().await?;
154
155 let storage_at = quantus_client.client().storage().at(latest_block_hash);
156
157 let account_info = storage_at
158 .fetch_or_default(&storage_addr)
159 .await
160 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch account info: {e:?}")))?;
161
162 log_verbose!("✅ Account info retrieved with storage query!");
163 log_verbose!("🔢 Nonce: {}", account_info.nonce);
164
165 Ok(account_info.nonce)
166}
167
168pub async fn handle_wallet_command(
170 command: WalletCommands,
171 node_url: &str,
172) -> crate::error::Result<()> {
173 match command {
174 WalletCommands::Create { name, password, derivation_path, no_derivation } => {
175 log_print!("🔐 Creating new quantum wallet...");
176
177 let wallet_manager = WalletManager::new()?;
178
179 let result = if no_derivation {
181 wallet_manager.create_wallet_no_derivation(&name, password.as_deref()).await
183 } else if derivation_path == DEFAULT_DERIVATION_PATH {
184 wallet_manager.create_wallet(&name, password.as_deref()).await
185 } else {
186 wallet_manager
187 .create_wallet_with_derivation_path(
188 &name,
189 password.as_deref(),
190 &derivation_path,
191 )
192 .await
193 };
194
195 match result {
196 Ok(wallet_info) => {
197 log_success!("Wallet name: {}", name.bright_green());
198 log_success!("Address: {}", wallet_info.address.bright_cyan());
199 log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
200 log_success!(
201 "Derivation path: {}",
202 wallet_info.derivation_path.bright_magenta()
203 );
204 log_success!(
205 "Created: {}",
206 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
207 );
208 log_success!("✅ Wallet created successfully!");
209 },
210 Err(e) => {
211 log_error!("{}", format!("❌ Failed to create wallet: {e}").red());
212 return Err(e);
213 },
214 }
215
216 Ok(())
217 },
218
219 WalletCommands::View { name, all } => {
220 log_print!("👁️ Viewing wallet information...");
221
222 let wallet_manager = WalletManager::new()?;
223
224 if all {
225 match wallet_manager.list_wallets() {
227 Ok(wallets) =>
228 if wallets.is_empty() {
229 log_print!("{}", "No wallets found.".dimmed());
230 } else {
231 log_print!("All wallets ({}):\n", wallets.len());
232
233 for (i, wallet) in wallets.iter().enumerate() {
234 log_print!(
235 "{}. {}",
236 (i + 1).to_string().bright_yellow(),
237 wallet.name.bright_green()
238 );
239 log_print!(" Address: {}", wallet.address.bright_cyan());
240 log_print!(" Type: {}", wallet.key_type.bright_yellow());
241 log_print!(
242 " Derivation Path: {}",
243 wallet.derivation_path.bright_magenta()
244 );
245 log_print!(
246 " Created: {}",
247 wallet
248 .created_at
249 .format("%Y-%m-%d %H:%M:%S UTC")
250 .to_string()
251 .dimmed()
252 );
253 if i < wallets.len() - 1 {
254 log_print!();
255 }
256 }
257 },
258 Err(e) => {
259 log_error!("{}", format!("❌ Failed to view wallets: {e}").red());
260 return Err(e);
261 },
262 }
263 } else if let Some(wallet_name) = name {
264 match wallet_manager.get_wallet(&wallet_name, None) {
266 Ok(Some(wallet_info)) => {
267 log_print!("Wallet Details:\n");
268 log_print!("Name: {}", wallet_info.name.bright_green());
269 log_print!("Address: {}", wallet_info.address.bright_cyan());
270 log_print!("Key Type: {}", wallet_info.key_type.bright_yellow());
271 log_print!(
272 "Derivation Path: {}",
273 wallet_info.derivation_path.bright_magenta()
274 );
275 log_print!(
276 "Created: {}",
277 wallet_info
278 .created_at
279 .format("%Y-%m-%d %H:%M:%S UTC")
280 .to_string()
281 .dimmed()
282 );
283
284 if wallet_info.address.contains("[") {
285 log_print!(
286 "\n{}",
287 "💡 To see the full address, use the export command with password"
288 .dimmed()
289 );
290 }
291 },
292 Ok(None) => {
293 log_error!("{}", format!("❌ Wallet '{wallet_name}' not found").red());
294 log_print!(
295 "Use {} to see available wallets",
296 "quantus wallet list".bright_green()
297 );
298 },
299 Err(e) => {
300 log_error!("{}", format!("❌ Failed to view wallet: {e}").red());
301 return Err(e);
302 },
303 }
304 } else {
305 log_print!(
306 "{}",
307 "Please specify a wallet name with --name or use --all to show all wallets"
308 .yellow()
309 );
310 log_print!("Examples:");
311 log_print!(" {}", "quantus wallet view --name my-wallet".bright_green());
312 log_print!(" {}", "quantus wallet view --all".bright_green());
313 }
314
315 Ok(())
316 },
317
318 WalletCommands::Export { name, password, format } => {
319 log_print!("📤 Exporting wallet...");
320
321 if format.to_lowercase() != "mnemonic" {
322 log_error!("Only 'mnemonic' export format is currently supported.");
323 return Err(crate::error::QuantusError::Generic(
324 "Export format not supported".to_string(),
325 ));
326 }
327
328 let wallet_manager = WalletManager::new()?;
329
330 match wallet_manager.export_mnemonic(&name, password.as_deref()) {
331 Ok(mnemonic) => {
332 log_success!("✅ Wallet exported successfully!");
333 log_print!("\nYour secret mnemonic phrase:");
334 log_print!("{}", "--------------------------------------------------".dimmed());
335 log_print!("{}", mnemonic.bright_yellow());
336 log_print!("{}", "--------------------------------------------------".dimmed());
337 log_print!(
338 "\n{}",
339 "⚠️ Keep this phrase safe and secret. Anyone with this phrase can access your funds."
340 .bright_red()
341 );
342 },
343 Err(e) => {
344 log_error!("{}", format!("❌ Failed to export wallet: {e}").red());
345 return Err(e);
346 },
347 }
348
349 Ok(())
350 },
351
352 WalletCommands::Import { name, mnemonic, password, derivation_path, no_derivation } => {
353 log_print!("📥 Importing wallet...");
354
355 let wallet_manager = WalletManager::new()?;
356
357 let mnemonic_phrase =
359 if let Some(mnemonic) = mnemonic { mnemonic } else { get_mnemonic_from_user()? };
360
361 let final_password =
363 crate::wallet::password::get_wallet_password(&name, password, None)?;
364
365 let result = if no_derivation {
367 wallet_manager
369 .import_wallet_no_derivation(&name, &mnemonic_phrase, Some(&final_password))
370 .await
371 } else if derivation_path == DEFAULT_DERIVATION_PATH {
372 wallet_manager
373 .import_wallet(&name, &mnemonic_phrase, Some(&final_password))
374 .await
375 } else {
376 wallet_manager
377 .import_wallet_with_derivation_path(
378 &name,
379 &mnemonic_phrase,
380 Some(&final_password),
381 &derivation_path,
382 )
383 .await
384 };
385
386 match result {
387 Ok(wallet_info) => {
388 log_success!("Wallet name: {}", name.bright_green());
389 log_success!("Address: {}", wallet_info.address.bright_cyan());
390 log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
391 log_success!(
392 "Derivation path: {}",
393 wallet_info.derivation_path.bright_magenta()
394 );
395 log_success!(
396 "Imported: {}",
397 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
398 );
399 log_success!("✅ Wallet imported successfully!");
400 },
401 Err(e) => {
402 log_error!("{}", format!("❌ Failed to import wallet: {e}").red());
403 return Err(e);
404 },
405 }
406
407 Ok(())
408 },
409
410 WalletCommands::FromSeed { name, seed, password } => {
411 log_print!("🌱 Creating wallet from seed...");
412
413 let wallet_manager = WalletManager::new()?;
414
415 let final_password =
417 crate::wallet::password::get_wallet_password(&name, password, None)?;
418
419 match wallet_manager
420 .create_wallet_from_seed(&name, &seed, Some(&final_password))
421 .await
422 {
423 Ok(wallet_info) => {
424 log_success!("Wallet name: {}", name.bright_green());
425 log_success!("Address: {}", wallet_info.address.bright_cyan());
426 log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
427 log_success!(
428 "Created: {}",
429 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
430 );
431 log_success!("✅ Wallet created from seed successfully!");
432 },
433 Err(e) => {
434 log_error!("{}", format!("❌ Failed to create wallet from seed: {e}").red());
435 return Err(e);
436 },
437 }
438
439 Ok(())
440 },
441
442 WalletCommands::List => {
443 log_print!("📋 Listing all wallets...");
444
445 let wallet_manager = WalletManager::new()?;
446
447 match wallet_manager.list_wallets() {
448 Ok(wallets) =>
449 if wallets.is_empty() {
450 log_print!("{}", "No wallets found.".dimmed());
451 log_print!(
452 "Create a new wallet with: {}",
453 "quantus wallet create --name <name>".bright_green()
454 );
455 } else {
456 log_print!("Found {} wallet(s):\n", wallets.len());
457
458 for (i, wallet) in wallets.iter().enumerate() {
459 log_print!(
460 "{}. {}",
461 (i + 1).to_string().bright_yellow(),
462 wallet.name.bright_green()
463 );
464 log_print!(" Address: {}", wallet.address.bright_cyan());
465 log_print!(" Type: {}", wallet.key_type.bright_yellow());
466 log_print!(
467 " Created: {}",
468 wallet
469 .created_at
470 .format("%Y-%m-%d %H:%M:%S UTC")
471 .to_string()
472 .dimmed()
473 );
474 if i < wallets.len() - 1 {
475 log_print!();
476 }
477 }
478
479 log_print!(
480 "\n{}",
481 "💡 Use 'quantus wallet view --name <wallet>' to see full details"
482 .dimmed()
483 );
484 },
485 Err(e) => {
486 log_error!("{}", format!("❌ Failed to list wallets: {e}").red());
487 return Err(e);
488 },
489 }
490
491 Ok(())
492 },
493
494 WalletCommands::Delete { name, force } => {
495 log_print!("🗑️ Deleting wallet...");
496
497 let wallet_manager = WalletManager::new()?;
498
499 match wallet_manager.get_wallet(&name, None) {
501 Ok(Some(wallet_info)) => {
502 log_print!("Wallet to delete:");
504 log_print!(" Name: {}", wallet_info.name.bright_green());
505 log_print!(" Address: {}", wallet_info.address.bright_cyan());
506 log_print!(" Type: {}", wallet_info.key_type.bright_yellow());
507 log_print!(
508 " Created: {}",
509 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
510 );
511
512 if !force {
514 log_print!("\n{}", "⚠️ This action cannot be undone!".bright_red());
515 log_print!("Type the wallet name to confirm deletion:");
516
517 print!("Confirm wallet name: ");
518 io::stdout().flush().unwrap();
519
520 let mut input = String::new();
521 io::stdin().read_line(&mut input).unwrap();
522 let input = input.trim();
523
524 if input != name {
525 log_print!(
526 "{}",
527 "❌ Wallet name doesn't match. Deletion cancelled.".red()
528 );
529 return Ok(());
530 }
531 }
532
533 match wallet_manager.delete_wallet(&name) {
535 Ok(true) => {
536 log_success!("✅ Wallet '{}' deleted successfully!", name);
537 },
538 Ok(false) => {
539 log_error!("{}", format!("❌ Wallet '{name}' was not found").red());
540 },
541 Err(e) => {
542 log_error!("{}", format!("❌ Failed to delete wallet: {e}").red());
543 return Err(e);
544 },
545 }
546 },
547 Ok(None) => {
548 log_error!("{}", format!("❌ Wallet '{name}' not found").red());
549 log_print!(
550 "Use {} to see available wallets",
551 "quantus wallet list".bright_green()
552 );
553 },
554 Err(e) => {
555 log_error!("{}", format!("❌ Failed to check wallet: {e}").red());
556 return Err(e);
557 },
558 }
559
560 Ok(())
561 },
562
563 WalletCommands::Nonce { address, wallet, password } => {
564 log_print!("🔢 Querying account nonce...");
565
566 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
567
568 let target_address = match (address, wallet) {
570 (Some(addr), _) => {
571 AccountId32::from_ss58check(&addr)
573 .map_err(|e| QuantusError::Generic(format!("Invalid address: {e:?}")))?;
574 addr
575 },
576 (None, Some(wallet_name)) => {
577 let keypair =
579 crate::wallet::load_keypair_from_wallet(&wallet_name, password, None)?;
580 keypair.to_account_id_ss58check()
581 },
582 (None, None) => {
583 unreachable!("Either --address or --wallet must be provided");
585 },
586 };
587
588 log_print!("Account: {}", target_address.bright_cyan());
589
590 match get_account_nonce(&quantus_client, &target_address).await {
591 Ok(nonce) => {
592 log_success!("Nonce: {}", nonce.to_string().bright_green());
593 },
594 Err(e) => {
595 log_print!("❌ Failed to get nonce: {}", e);
596 return Err(e);
597 },
598 }
599
600 Ok(())
601 },
602 }
603}