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},
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
27 View {
29 #[arg(short, long)]
31 name: Option<String>,
32
33 #[arg(short, long)]
35 all: bool,
36 },
37
38 Export {
40 #[arg(short, long)]
42 name: String,
43
44 #[arg(short, long)]
46 password: Option<String>,
47
48 #[arg(short, long, default_value = "mnemonic")]
50 format: String,
51 },
52
53 Import {
55 #[arg(short, long)]
57 name: String,
58
59 #[arg(short, long)]
61 mnemonic: Option<String>,
62
63 #[arg(short, long)]
65 password: Option<String>,
66 },
67
68 FromSeed {
70 #[arg(short, long)]
72 name: String,
73
74 #[arg(short, long)]
76 seed: String,
77
78 #[arg(short, long)]
80 password: Option<String>,
81 },
82
83 List,
85
86 Delete {
88 #[arg(short, long)]
90 name: String,
91
92 #[arg(short, long)]
94 force: bool,
95 },
96
97 Nonce {
99 #[arg(short, long)]
101 address: Option<String>,
102
103 #[arg(short, long, required_unless_present("address"))]
105 wallet: Option<String>,
106
107 #[arg(short, long)]
109 password: Option<String>,
110 },
111}
112
113pub async fn get_account_nonce(
115 quantus_client: &crate::chain::client::QuantusClient,
116 account_address: &str,
117) -> crate::error::Result<u32> {
118 log_verbose!("#️⃣ Querying nonce for account: {}", account_address.bright_green());
119
120 let (account_id_sp, _) = AccountId32::from_ss58check_with_version(account_address)
122 .map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
123
124 log_verbose!("🔍 SP Account ID: {:?}", account_id_sp);
125
126 let account_bytes: [u8; 32] = *account_id_sp.as_ref();
128 let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
129
130 log_verbose!("🔍 SubXT Account ID: {:?}", account_id);
131
132 use quantus_subxt::api;
134 let storage_addr = api::storage().system().account(account_id);
135
136 let latest_block_hash = quantus_client.get_latest_block().await?;
138
139 let storage_at = quantus_client.client().storage().at(latest_block_hash);
140
141 let account_info = storage_at
142 .fetch_or_default(&storage_addr)
143 .await
144 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch account info: {e:?}")))?;
145
146 log_verbose!("✅ Account info retrieved with storage query!");
147 log_verbose!("🔢 Nonce: {}", account_info.nonce);
148
149 Ok(account_info.nonce)
150}
151
152pub async fn handle_wallet_command(
154 command: WalletCommands,
155 node_url: &str,
156) -> crate::error::Result<()> {
157 match command {
158 WalletCommands::Create { name, password } => {
159 log_print!("🔐 Creating new quantum wallet...");
160
161 let wallet_manager = WalletManager::new()?;
162
163 match wallet_manager.create_wallet(&name, password.as_deref()).await {
164 Ok(wallet_info) => {
165 log_success!("Wallet name: {}", name.bright_green());
166 log_success!("Address: {}", wallet_info.address.bright_cyan());
167 log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
168 log_success!(
169 "Created: {}",
170 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
171 );
172 log_success!("✅ Wallet created successfully!");
173 },
174 Err(e) => {
175 log_error!("{}", format!("❌ Failed to create wallet: {e}").red());
176 return Err(e);
177 },
178 }
179
180 Ok(())
181 },
182
183 WalletCommands::View { name, all } => {
184 log_print!("👁️ Viewing wallet information...");
185
186 let wallet_manager = WalletManager::new()?;
187
188 if all {
189 match wallet_manager.list_wallets() {
191 Ok(wallets) =>
192 if wallets.is_empty() {
193 log_print!("{}", "No wallets found.".dimmed());
194 } else {
195 log_print!("All wallets ({}):\n", wallets.len());
196
197 for (i, wallet) in wallets.iter().enumerate() {
198 log_print!(
199 "{}. {}",
200 (i + 1).to_string().bright_yellow(),
201 wallet.name.bright_green()
202 );
203 log_print!(" Address: {}", wallet.address.bright_cyan());
204 log_print!(" Type: {}", wallet.key_type.bright_yellow());
205 log_print!(
206 " Created: {}",
207 wallet
208 .created_at
209 .format("%Y-%m-%d %H:%M:%S UTC")
210 .to_string()
211 .dimmed()
212 );
213 if i < wallets.len() - 1 {
214 log_print!();
215 }
216 }
217 },
218 Err(e) => {
219 log_error!("{}", format!("❌ Failed to view wallets: {e}").red());
220 return Err(e);
221 },
222 }
223 } else if let Some(wallet_name) = name {
224 match wallet_manager.get_wallet(&wallet_name, None) {
226 Ok(Some(wallet_info)) => {
227 log_print!("Wallet Details:\n");
228 log_print!("Name: {}", wallet_info.name.bright_green());
229 log_print!("Address: {}", wallet_info.address.bright_cyan());
230 log_print!("Key Type: {}", wallet_info.key_type.bright_yellow());
231 log_print!(
232 "Created: {}",
233 wallet_info
234 .created_at
235 .format("%Y-%m-%d %H:%M:%S UTC")
236 .to_string()
237 .dimmed()
238 );
239
240 if wallet_info.address.contains("[") {
241 log_print!(
242 "\n{}",
243 "💡 To see the full address, use the export command with password"
244 .dimmed()
245 );
246 }
247 },
248 Ok(None) => {
249 log_error!("{}", format!("❌ Wallet '{wallet_name}' not found").red());
250 log_print!(
251 "Use {} to see available wallets",
252 "quantus wallet list".bright_green()
253 );
254 },
255 Err(e) => {
256 log_error!("{}", format!("❌ Failed to view wallet: {e}").red());
257 return Err(e);
258 },
259 }
260 } else {
261 log_print!(
262 "{}",
263 "Please specify a wallet name with --name or use --all to show all wallets"
264 .yellow()
265 );
266 log_print!("Examples:");
267 log_print!(" {}", "quantus wallet view --name my-wallet".bright_green());
268 log_print!(" {}", "quantus wallet view --all".bright_green());
269 }
270
271 Ok(())
272 },
273
274 WalletCommands::Export { name, password, format } => {
275 log_print!("📤 Exporting wallet...");
276
277 if format.to_lowercase() != "mnemonic" {
278 log_error!("Only 'mnemonic' export format is currently supported.");
279 return Err(crate::error::QuantusError::Generic(
280 "Export format not supported".to_string(),
281 ));
282 }
283
284 let wallet_manager = WalletManager::new()?;
285
286 match wallet_manager.export_mnemonic(&name, password.as_deref()) {
287 Ok(mnemonic) => {
288 log_success!("✅ Wallet exported successfully!");
289 log_print!("\nYour secret mnemonic phrase:");
290 log_print!("{}", "--------------------------------------------------".dimmed());
291 log_print!("{}", mnemonic.bright_yellow());
292 log_print!("{}", "--------------------------------------------------".dimmed());
293 log_print!(
294 "\n{}",
295 "⚠️ Keep this phrase safe and secret. Anyone with this phrase can access your funds."
296 .bright_red()
297 );
298 },
299 Err(e) => {
300 log_error!("{}", format!("❌ Failed to export wallet: {e}").red());
301 return Err(e);
302 },
303 }
304
305 Ok(())
306 },
307
308 WalletCommands::Import { name, mnemonic, password } => {
309 log_print!("📥 Importing wallet...");
310
311 let wallet_manager = WalletManager::new()?;
312
313 let mnemonic_phrase =
315 if let Some(mnemonic) = mnemonic { mnemonic } else { get_mnemonic_from_user()? };
316
317 let final_password =
319 crate::wallet::password::get_wallet_password(&name, password, None)?;
320
321 match wallet_manager
322 .import_wallet(&name, &mnemonic_phrase, Some(&final_password))
323 .await
324 {
325 Ok(wallet_info) => {
326 log_success!("Wallet name: {}", name.bright_green());
327 log_success!("Address: {}", wallet_info.address.bright_cyan());
328 log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
329 log_success!(
330 "Imported: {}",
331 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
332 );
333 log_success!("✅ Wallet imported successfully!");
334 },
335 Err(e) => {
336 log_error!("{}", format!("❌ Failed to import wallet: {e}").red());
337 return Err(e);
338 },
339 }
340
341 Ok(())
342 },
343
344 WalletCommands::FromSeed { name, seed, password } => {
345 log_print!("🌱 Creating wallet from seed...");
346
347 let wallet_manager = WalletManager::new()?;
348
349 let final_password =
351 crate::wallet::password::get_wallet_password(&name, password, None)?;
352
353 match wallet_manager
354 .create_wallet_from_seed(&name, &seed, Some(&final_password))
355 .await
356 {
357 Ok(wallet_info) => {
358 log_success!("Wallet name: {}", name.bright_green());
359 log_success!("Address: {}", wallet_info.address.bright_cyan());
360 log_success!("Key type: {}", wallet_info.key_type.bright_yellow());
361 log_success!(
362 "Created: {}",
363 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
364 );
365 log_success!("✅ Wallet created from seed successfully!");
366 },
367 Err(e) => {
368 log_error!("{}", format!("❌ Failed to create wallet from seed: {e}").red());
369 return Err(e);
370 },
371 }
372
373 Ok(())
374 },
375
376 WalletCommands::List => {
377 log_print!("📋 Listing all wallets...");
378
379 let wallet_manager = WalletManager::new()?;
380
381 match wallet_manager.list_wallets() {
382 Ok(wallets) =>
383 if wallets.is_empty() {
384 log_print!("{}", "No wallets found.".dimmed());
385 log_print!(
386 "Create a new wallet with: {}",
387 "quantus wallet create --name <name>".bright_green()
388 );
389 } else {
390 log_print!("Found {} wallet(s):\n", wallets.len());
391
392 for (i, wallet) in wallets.iter().enumerate() {
393 log_print!(
394 "{}. {}",
395 (i + 1).to_string().bright_yellow(),
396 wallet.name.bright_green()
397 );
398 log_print!(" Address: {}", wallet.address.bright_cyan());
399 log_print!(" Type: {}", wallet.key_type.bright_yellow());
400 log_print!(
401 " Created: {}",
402 wallet
403 .created_at
404 .format("%Y-%m-%d %H:%M:%S UTC")
405 .to_string()
406 .dimmed()
407 );
408 if i < wallets.len() - 1 {
409 log_print!();
410 }
411 }
412
413 log_print!(
414 "\n{}",
415 "💡 Use 'quantus wallet view --name <wallet>' to see full details"
416 .dimmed()
417 );
418 },
419 Err(e) => {
420 log_error!("{}", format!("❌ Failed to list wallets: {e}").red());
421 return Err(e);
422 },
423 }
424
425 Ok(())
426 },
427
428 WalletCommands::Delete { name, force } => {
429 log_print!("🗑️ Deleting wallet...");
430
431 let wallet_manager = WalletManager::new()?;
432
433 match wallet_manager.get_wallet(&name, None) {
435 Ok(Some(wallet_info)) => {
436 log_print!("Wallet to delete:");
438 log_print!(" Name: {}", wallet_info.name.bright_green());
439 log_print!(" Address: {}", wallet_info.address.bright_cyan());
440 log_print!(" Type: {}", wallet_info.key_type.bright_yellow());
441 log_print!(
442 " Created: {}",
443 wallet_info.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string().dimmed()
444 );
445
446 if !force {
448 log_print!("\n{}", "⚠️ This action cannot be undone!".bright_red());
449 log_print!("Type the wallet name to confirm deletion:");
450
451 print!("Confirm wallet name: ");
452 io::stdout().flush().unwrap();
453
454 let mut input = String::new();
455 io::stdin().read_line(&mut input).unwrap();
456 let input = input.trim();
457
458 if input != name {
459 log_print!(
460 "{}",
461 "❌ Wallet name doesn't match. Deletion cancelled.".red()
462 );
463 return Ok(());
464 }
465 }
466
467 match wallet_manager.delete_wallet(&name) {
469 Ok(true) => {
470 log_success!("✅ Wallet '{}' deleted successfully!", name);
471 },
472 Ok(false) => {
473 log_error!("{}", format!("❌ Wallet '{name}' was not found").red());
474 },
475 Err(e) => {
476 log_error!("{}", format!("❌ Failed to delete wallet: {e}").red());
477 return Err(e);
478 },
479 }
480 },
481 Ok(None) => {
482 log_error!("{}", format!("❌ Wallet '{name}' not found").red());
483 log_print!(
484 "Use {} to see available wallets",
485 "quantus wallet list".bright_green()
486 );
487 },
488 Err(e) => {
489 log_error!("{}", format!("❌ Failed to check wallet: {e}").red());
490 return Err(e);
491 },
492 }
493
494 Ok(())
495 },
496
497 WalletCommands::Nonce { address, wallet, password } => {
498 log_print!("🔢 Querying account nonce...");
499
500 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
501
502 let target_address = match (address, wallet) {
504 (Some(addr), _) => {
505 AccountId32::from_ss58check(&addr)
507 .map_err(|e| QuantusError::Generic(format!("Invalid address: {e:?}")))?;
508 addr
509 },
510 (None, Some(wallet_name)) => {
511 let keypair =
513 crate::wallet::load_keypair_from_wallet(&wallet_name, password, None)?;
514 keypair.to_account_id_ss58check()
515 },
516 (None, None) => {
517 unreachable!("Either --address or --wallet must be provided");
519 },
520 };
521
522 log_print!("Account: {}", target_address.bright_cyan());
523
524 match get_account_nonce(&quantus_client, &target_address).await {
525 Ok(nonce) => {
526 log_success!("Nonce: {}", nonce.to_string().bright_green());
527 },
528 Err(e) => {
529 log_print!("❌ Failed to get nonce: {}", e);
530 return Err(e);
531 },
532 }
533
534 Ok(())
535 },
536 }
537}