quantus_cli/cli/
recovery.rs

1use crate::{
2	chain::quantus_subxt,
3	cli::{common::resolve_address, progress_spinner::wait_for_tx_confirmation},
4	log_error, log_print, log_success,
5};
6use clap::Subcommand;
7// no colored output needed here
8use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
9
10// Base unit (QUAN) decimals for amount conversions
11const QUAN_DECIMALS: u128 = 1_000_000_000_000; // 10^12
12
13/// Recovery-related commands
14#[derive(Subcommand, Debug)]
15pub enum RecoveryCommands {
16	/// Initiate recovery (rescuer starts)
17	Initiate {
18		/// Rescuer wallet name
19		#[arg(long)]
20		rescuer: String,
21		/// Lost account (SS58 or wallet name)
22		#[arg(long)]
23		lost: String,
24		/// Password for rescuer wallet
25		#[arg(short, long)]
26		password: Option<String>,
27		/// Read password from file (for scripting)
28		#[arg(long)]
29		password_file: Option<String>,
30	},
31
32	/// Vouch for a recovery attempt (friend)
33	Vouch {
34		/// Friend wallet name (who vouches)
35		#[arg(long)]
36		friend: String,
37		/// Lost account (SS58 or wallet name)
38		#[arg(long)]
39		lost: String,
40		/// Rescuer account (SS58 or wallet name)
41		#[arg(long)]
42		rescuer: String,
43		/// Password for friend wallet
44		#[arg(short, long)]
45		password: Option<String>,
46		/// Read password from file
47		#[arg(long)]
48		password_file: Option<String>,
49	},
50
51	/// Claim recovery (rescuer claims after threshold and delay)
52	Claim {
53		/// Rescuer wallet name
54		#[arg(long)]
55		rescuer: String,
56		/// Lost account (SS58 or wallet name)
57		#[arg(long)]
58		lost: String,
59		/// Password for rescuer wallet
60		#[arg(short, long)]
61		password: Option<String>,
62		/// Read password from file
63		#[arg(long)]
64		password_file: Option<String>,
65	},
66
67	/// Close an active recovery (lost account stops a malicious attempt)
68	Close {
69		/// Lost wallet name (the recoverable account)
70		#[arg(long)]
71		lost: String,
72		/// Rescuer account (SS58 or wallet name)
73		#[arg(long)]
74		rescuer: String,
75		/// Password for lost wallet
76		#[arg(short, long)]
77		password: Option<String>,
78		/// Read password from file
79		#[arg(long)]
80		password_file: Option<String>,
81	},
82
83	/// Cancel recovered proxy (rescuer disables their own proxy)
84	CancelProxy {
85		/// Rescuer wallet name
86		#[arg(long)]
87		rescuer: String,
88		/// Lost account (SS58 or wallet name)
89		#[arg(long)]
90		lost: String,
91		/// Password for rescuer wallet
92		#[arg(short, long)]
93		password: Option<String>,
94		/// Read password from file
95		#[arg(long)]
96		password_file: Option<String>,
97	},
98
99	/// Query: active recovery info
100	Active {
101		/// Lost account (SS58 or wallet name)
102		#[arg(long)]
103		lost: String,
104		/// Rescuer account (SS58 or wallet name)
105		#[arg(long)]
106		rescuer: String,
107	},
108
109	/// Query: proxy-of (rescuer -> lost)
110	ProxyOf {
111		/// Rescuer account (SS58 or wallet name)
112		#[arg(long)]
113		rescuer: String,
114	},
115
116	/// Query: recovery config (recoverable)
117	Config {
118		/// Account to query (SS58 or wallet name)
119		#[arg(long)]
120		account: String,
121	},
122
123	/// Recover all funds from the lost account to a destination
124	RecoverAll {
125		/// Rescuer wallet name
126		#[arg(long)]
127		rescuer: String,
128		/// Lost account (SS58 or wallet name)
129		#[arg(long)]
130		lost: String,
131		/// Destination to receive the recovered funds
132		#[arg(long)]
133		dest: String,
134		/// Keep the lost account alive
135		#[arg(long, default_value_t = true)]
136		keep_alive: bool,
137		/// Password for rescuer wallet
138		#[arg(short, long)]
139		password: Option<String>,
140		/// Read password from file
141		#[arg(long)]
142		password_file: Option<String>,
143	},
144
145	/// Recover a specific amount (in QUAN units) from the lost account to destination
146	RecoverAmount {
147		/// Rescuer wallet name
148		#[arg(long)]
149		rescuer: String,
150		/// Lost account (SS58 or wallet name)
151		#[arg(long)]
152		lost: String,
153		/// Destination to receive the recovered funds
154		#[arg(long)]
155		dest: String,
156		/// Amount in QUAN (human units) - multiplied by chain decimals
157		#[arg(long, value_name = "AMOUNT_QUAN")]
158		amount_quan: u128,
159		/// Keep the lost account alive
160		#[arg(long, default_value_t = true)]
161		keep_alive: bool,
162		/// Password for rescuer wallet
163		#[arg(short, long)]
164		password: Option<String>,
165		/// Read password from file
166		#[arg(long)]
167		password_file: Option<String>,
168	},
169}
170
171pub async fn handle_recovery_command(
172	command: RecoveryCommands,
173	node_url: &str,
174) -> crate::error::Result<()> {
175	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
176
177	match command {
178		RecoveryCommands::Initiate { rescuer, lost, password, password_file } => {
179			let rescuer_key =
180				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
181			let rescuer_addr = rescuer_key.to_account_id_ss58check();
182			log_print!("🔑 Rescuer: {}", rescuer);
183			log_print!("🔑 Rescuer address: {}", rescuer_addr);
184			let lost_resolved = resolve_address(&lost)?;
185			let lost_id_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
186				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
187			})?;
188			let lost_id_bytes: [u8; 32] = *lost_id_sp.as_ref();
189			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_id_bytes);
190			let call = quantus_subxt::api::tx()
191				.recovery()
192				.initiate_recovery(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id));
193			let tx_hash =
194				crate::cli::common::submit_transaction(&quantus_client, &rescuer_key, call, None)
195					.await
196					.map_err(|e| {
197						crate::error::QuantusError::NetworkError(format!(
198							"Failed to submit initiate_recovery transaction: {e}"
199						))
200					})?;
201
202			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
203			log_success!("✅ Initiate recovery submitted successfully");
204
205			let confirmation_result =
206				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
207			match confirmation_result {
208				Ok(true) => {
209					log_success!("✅ Transaction confirmed");
210					Ok(())
211				},
212				Ok(false) => {
213					log_error!("⚠️  Transaction may not have been confirmed");
214					Ok(())
215				},
216				Err(e) => {
217					log_error!("❌ Failed to confirm transaction: {e}");
218					Err(e)
219				},
220			}
221		},
222
223		RecoveryCommands::Vouch { friend, lost, rescuer, password, password_file } => {
224			let friend_key =
225				crate::wallet::load_keypair_from_wallet(&friend, password, password_file)?;
226			let lost_resolved = resolve_address(&lost)?;
227			let rescuer_resolved = resolve_address(&rescuer)?;
228			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
229				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
230			})?;
231			let lost_bytes: [u8; 32] = *lost_sp.as_ref();
232			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_bytes);
233			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
234				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
235			})?;
236			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
237			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
238			let call = quantus_subxt::api::tx().recovery().vouch_recovery(
239				subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id),
240				subxt::ext::subxt_core::utils::MultiAddress::Id(rescuer_id),
241			);
242			let tx_hash =
243				crate::cli::common::submit_transaction(&quantus_client, &friend_key, call, None)
244					.await
245					.map_err(|e| {
246						crate::error::QuantusError::NetworkError(format!(
247							"Failed to submit vouch_recovery transaction: {e}"
248						))
249					})?;
250
251			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
252			log_success!("✅ Vouch submitted successfully");
253
254			let confirmation_result =
255				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
256			match confirmation_result {
257				Ok(true) => {
258					log_success!("✅ Transaction confirmed");
259					Ok(())
260				},
261				Ok(false) => {
262					log_error!("⚠️  Transaction may not have been confirmed");
263					Ok(())
264				},
265				Err(e) => {
266					log_error!("❌ Failed to confirm transaction: {e}");
267					Err(e)
268				},
269			}
270		},
271
272		RecoveryCommands::Claim { rescuer, lost, password, password_file } => {
273			let rescuer_key =
274				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
275			let lost_resolved = resolve_address(&lost)?;
276			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
277				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
278			})?;
279			let lost_bytes: [u8; 32] = *lost_sp.as_ref();
280			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_bytes);
281			let call = quantus_subxt::api::tx()
282				.recovery()
283				.claim_recovery(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id));
284			let tx_hash =
285				crate::cli::common::submit_transaction(&quantus_client, &rescuer_key, call, None)
286					.await
287					.map_err(|e| {
288						crate::error::QuantusError::NetworkError(format!(
289							"Failed to submit claim_recovery transaction: {e}"
290						))
291					})?;
292
293			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
294			log_success!("✅ Claim submitted successfully");
295
296			let confirmation_result =
297				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
298			match confirmation_result {
299				Ok(true) => {
300					log_success!("✅ Transaction confirmed");
301					Ok(())
302				},
303				Ok(false) => {
304					log_error!("⚠️  Transaction may not have been confirmed");
305					Ok(())
306				},
307				Err(e) => {
308					log_error!("❌ Failed to confirm transaction: {e}");
309					Err(e)
310				},
311			}
312		},
313
314		RecoveryCommands::RecoverAll {
315			rescuer,
316			lost,
317			dest,
318			keep_alive,
319			password,
320			password_file,
321		} => {
322			use quantus_subxt::api::runtime_types::pallet_balances::pallet::Call as BalancesCall;
323
324			let rescuer_key =
325				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
326			let rescuer_addr = rescuer_key.to_account_id_ss58check();
327			log_print!("🔑 Rescuer: {}", rescuer);
328			log_print!("🔑 Rescuer address: {}", rescuer_addr);
329
330			let lost_resolved = resolve_address(&lost)?;
331			let dest_resolved = resolve_address(&dest)?;
332			log_print!("🆘 Lost input: {} -> {}", lost, lost_resolved);
333			log_print!("🎯 Dest input: {} -> {}", dest, dest_resolved);
334			log_print!("🛟 keep_alive: {}", keep_alive);
335
336			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
337				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
338			})?;
339			let dest_sp = SpAccountId32::from_ss58check(&dest_resolved).map_err(|e| {
340				crate::error::QuantusError::Generic(format!("Invalid dest address: {e:?}"))
341			})?;
342
343			let lost_id_bytes: [u8; 32] = *lost_sp.as_ref();
344			let dest_id_bytes: [u8; 32] = *dest_sp.as_ref();
345			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_id_bytes);
346			let dest_id = subxt::ext::subxt_core::utils::AccountId32::from(dest_id_bytes);
347
348			// Check proxy mapping for rescuer
349			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_addr).map_err(|e| {
350				crate::error::QuantusError::Generic(format!(
351					"Invalid rescuer address from wallet: {e:?}"
352				))
353			})?;
354			let rescuer_id_bytes: [u8; 32] = *rescuer_sp.as_ref();
355			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_id_bytes);
356			let proxy_storage = quantus_subxt::api::storage().recovery().proxy(rescuer_id);
357			let latest = quantus_client.get_latest_block().await?;
358			let proxy_result =
359				quantus_client.client().storage().at(latest).fetch(&proxy_storage).await;
360			let proxy_of = match proxy_result {
361				Ok(Some(proxy)) => {
362					log_print!("🧩 Proxy mapping: rescuer proxies -> {}", format!("{}", proxy));
363					Some(proxy)
364				},
365				Ok(None) => {
366					log_error!(
367						"❌ No proxy mapping found for rescuer - recovery not set up properly"
368					);
369					return Err(crate::error::QuantusError::Generic(
370						"Rescuer has no proxy mapping. Recovery process may not be properly set up.".to_string()
371					));
372				},
373				Err(e) => {
374					log_error!("❌ Proxy mapping fetch error: {:?}", e);
375					return Err(crate::error::QuantusError::NetworkError(format!(
376						"Failed to check proxy mapping: {e:?}"
377					)));
378				},
379			};
380
381			// Validate that the proxy points to the correct lost account
382			if let Some(proxy) = proxy_of {
383				let proxy_addr = format!("{proxy}");
384				if proxy_addr != lost_resolved {
385					log_error!(
386						"❌ Proxy mismatch! Rescuer proxies {} but we're trying to recover {}",
387						proxy_addr,
388						lost_resolved
389					);
390					return Err(crate::error::QuantusError::Generic(format!(
391						"Proxy mismatch: rescuer proxies {proxy_addr} but target is {lost_resolved}"
392					)));
393				}
394				log_print!("✅ Proxy validation successful");
395			}
396
397			let inner_call = quantus_subxt::api::Call::Balances(BalancesCall::transfer_all {
398				dest: subxt::ext::subxt_core::utils::MultiAddress::Id(dest_id),
399				keep_alive,
400			});
401			log_print!("🧱 Inner call: Balances.transfer_all(keep_alive={})", keep_alive);
402
403			let call = quantus_subxt::api::tx()
404				.recovery()
405				.as_recovered(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id), inner_call);
406
407			let tx_hash = match crate::cli::common::submit_transaction(
408				&quantus_client,
409				&rescuer_key,
410				call,
411				None,
412			)
413			.await
414			{
415				Ok(h) => h,
416				Err(e) => {
417					log_error!("❌ Submit error (recover_all): {:?}", e);
418					return Err(e);
419				},
420			};
421
422			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
423			log_success!("✅ recover_all submitted successfully");
424
425			let confirmation_result =
426				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
427			match confirmation_result {
428				Ok(true) => {
429					log_success!("✅ Transaction confirmed");
430					Ok(())
431				},
432				Ok(false) => {
433					log_error!("⚠️  Transaction may not have been confirmed");
434					Ok(())
435				},
436				Err(e) => {
437					log_error!("❌ Failed to confirm transaction: {e}");
438					Err(e)
439				},
440			}
441		},
442
443		RecoveryCommands::RecoverAmount {
444			rescuer,
445			lost,
446			dest,
447			amount_quan,
448			keep_alive,
449			password,
450			password_file,
451		} => {
452			use quantus_subxt::api::runtime_types::pallet_balances::pallet::Call as BalancesCall;
453
454			let rescuer_key =
455				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
456
457			let rescuer_addr = rescuer_key.to_account_id_ss58check();
458			log_print!("🔑 Rescuer: {}", rescuer);
459			log_print!("🔑 Rescuer address: {}", rescuer_addr);
460
461			let lost_resolved = resolve_address(&lost)?;
462			let dest_resolved = resolve_address(&dest)?;
463			log_print!("🆘 Lost input: {} -> {}", lost, lost_resolved);
464			log_print!("🎯 Dest input: {} -> {}", dest, dest_resolved);
465			log_print!("💵 amount_quan: {} (QUAN_DECIMALS={})", amount_quan, QUAN_DECIMALS);
466			log_print!("🛟 keep_alive: {}", keep_alive);
467
468			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
469				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
470			})?;
471			let dest_sp = SpAccountId32::from_ss58check(&dest_resolved).map_err(|e| {
472				crate::error::QuantusError::Generic(format!("Invalid dest address: {e:?}"))
473			})?;
474
475			let lost_id_bytes: [u8; 32] = *lost_sp.as_ref();
476			let dest_id_bytes: [u8; 32] = *dest_sp.as_ref();
477			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_id_bytes);
478			let dest_id = subxt::ext::subxt_core::utils::AccountId32::from(dest_id_bytes);
479
480			let amount_plancks = amount_quan.saturating_mul(QUAN_DECIMALS);
481			log_print!("💵 amount_plancks: {}", amount_plancks);
482
483			let latest = quantus_client.get_latest_block().await?;
484
485			// Check account balance before attempting transfer
486			log_print!("💰 Checking lost account balance...");
487			let balance_result = quantus_client
488				.client()
489				.storage()
490				.at(latest)
491				.fetch(&quantus_subxt::api::storage().system().account(lost_id.clone()))
492				.await;
493
494			let account_info = match balance_result {
495				Ok(Some(info)) => info,
496				Ok(None) => {
497					log_error!("❌ Lost account not found in storage");
498					return Err(crate::error::QuantusError::Generic(
499						"Lost account not found in storage".to_string(),
500					));
501				},
502				Err(e) => {
503					log_error!("❌ Failed to fetch account balance: {:?}", e);
504					return Err(crate::error::QuantusError::NetworkError(format!(
505						"Failed to fetch account balance: {e:?}"
506					)));
507				},
508			};
509
510			let available_balance = account_info.data.free;
511			log_print!("💰 Available balance: {} plancks", available_balance);
512
513			if available_balance < amount_plancks {
514				log_error!(
515					"❌ Insufficient funds! Account has {} plancks but needs {} plancks",
516					available_balance,
517					amount_plancks
518				);
519				return Err(crate::error::QuantusError::Generic(format!(
520					"Insufficient funds: account has {available_balance} plancks but transfer requires {amount_plancks} plancks"
521				)));
522			}
523
524			log_print!("✅ Balance validation successful - sufficient funds available");
525
526			let inner_call =
527				quantus_subxt::api::Call::Balances(BalancesCall::transfer_keep_alive {
528					dest: subxt::ext::subxt_core::utils::MultiAddress::Id(dest_id),
529					value: amount_plancks,
530				});
531
532			let call = quantus_subxt::api::tx()
533				.recovery()
534				.as_recovered(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id), inner_call);
535
536			let tx_hash = match crate::cli::common::submit_transaction(
537				&quantus_client,
538				&rescuer_key,
539				call,
540				None,
541			)
542			.await
543			{
544				Ok(h) => h,
545				Err(e) => {
546					log_error!("❌ Submit error (recover_amount): {:?}", e);
547					return Err(e);
548				},
549			};
550
551			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
552			log_success!("✅ recover_amount submitted successfully");
553
554			let confirmation_result =
555				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
556			match confirmation_result {
557				Ok(true) => {
558					log_success!("✅ Transaction confirmed");
559					Ok(())
560				},
561				Ok(false) => {
562					log_error!("⚠️  Transaction may not have been confirmed");
563					Ok(())
564				},
565				Err(e) => {
566					log_error!("❌ Failed to confirm transaction: {e}");
567					Err(e)
568				},
569			}
570		},
571
572		RecoveryCommands::Close { lost, rescuer, password, password_file } => {
573			let lost_key = crate::wallet::load_keypair_from_wallet(&lost, password, password_file)?;
574			let rescuer_resolved = resolve_address(&rescuer)?;
575			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
576				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
577			})?;
578			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
579			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
580			let call = quantus_subxt::api::tx()
581				.recovery()
582				.close_recovery(subxt::ext::subxt_core::utils::MultiAddress::Id(rescuer_id));
583			let tx_hash =
584				crate::cli::common::submit_transaction(&quantus_client, &lost_key, call, None)
585					.await
586					.map_err(|e| {
587						crate::error::QuantusError::NetworkError(format!(
588							"Failed to submit close_recovery transaction: {e}"
589						))
590					})?;
591
592			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
593			log_success!("✅ close_recovery submitted successfully");
594
595			let confirmation_result =
596				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
597			match confirmation_result {
598				Ok(true) => {
599					log_success!("✅ Transaction confirmed");
600					Ok(())
601				},
602				Ok(false) => {
603					log_error!("⚠️  Transaction may not have been confirmed");
604					Ok(())
605				},
606				Err(e) => {
607					log_error!("❌ Failed to confirm transaction: {e}");
608					Err(e)
609				},
610			}
611		},
612
613		RecoveryCommands::CancelProxy { rescuer, lost, password, password_file } => {
614			let rescuer_key =
615				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
616			let lost_resolved = resolve_address(&lost)?;
617			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
618				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
619			})?;
620			let lost_bytes: [u8; 32] = *lost_sp.as_ref();
621			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_bytes);
622			let call = quantus_subxt::api::tx()
623				.recovery()
624				.cancel_recovered(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id));
625			let tx_hash =
626				crate::cli::common::submit_transaction(&quantus_client, &rescuer_key, call, None)
627					.await
628					.map_err(|e| {
629						crate::error::QuantusError::NetworkError(format!(
630							"Failed to submit cancel_recovered transaction: {e}"
631						))
632					})?;
633
634			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
635			log_success!("✅ cancel_recovered submitted successfully");
636
637			let confirmation_result =
638				wait_for_tx_confirmation(quantus_client.client(), tx_hash).await;
639			match confirmation_result {
640				Ok(true) => {
641					log_success!("✅ Transaction confirmed");
642					Ok(())
643				},
644				Ok(false) => {
645					log_error!("⚠️  Transaction may not have been confirmed");
646					Ok(())
647				},
648				Err(e) => {
649					log_error!("❌ Failed to confirm transaction: {e}");
650					Err(e)
651				},
652			}
653		},
654
655		RecoveryCommands::Active { lost, rescuer } => {
656			let lost_resolved = resolve_address(&lost)?;
657			let rescuer_resolved = resolve_address(&rescuer)?;
658			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
659				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
660			})?;
661			let lost_bytes: [u8; 32] = *lost_sp.as_ref();
662			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_bytes);
663			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
664				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
665			})?;
666			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
667			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
668			let storage_addr =
669				quantus_subxt::api::storage().recovery().active_recoveries(lost_id, rescuer_id);
670			let latest = quantus_client.get_latest_block().await?;
671			let value = quantus_client
672				.client()
673				.storage()
674				.at(latest)
675				.fetch(&storage_addr)
676				.await
677				.map_err(|e| {
678					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
679				})?;
680			if let Some(active) = value {
681				log_print!(
682					"{}",
683					serde_json::json!({
684						"created": active.created,
685						"deposit": active.deposit,
686						"friends_vouched": active.friends.0.len(),
687					})
688				);
689			} else {
690				log_print!("{}", serde_json::json!({"active": false}));
691			}
692			Ok(())
693		},
694
695		RecoveryCommands::ProxyOf { rescuer } => {
696			let rescuer_resolved = resolve_address(&rescuer)?;
697			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
698				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
699			})?;
700			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
701			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
702			let storage_addr = quantus_subxt::api::storage().recovery().proxy(rescuer_id);
703			let latest = quantus_client.get_latest_block().await?;
704			let value = quantus_client
705				.client()
706				.storage()
707				.at(latest)
708				.fetch(&storage_addr)
709				.await
710				.map_err(|e| {
711					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
712				})?;
713			if let Some(lost_id) = value {
714				log_print!("{}", serde_json::json!({"lost": format!("{}", lost_id)}));
715			} else {
716				log_print!("{}", serde_json::json!({"lost": null}));
717			}
718			Ok(())
719		},
720
721		RecoveryCommands::Config { account } => {
722			let account_resolved = resolve_address(&account)?;
723			let account_sp = SpAccountId32::from_ss58check(&account_resolved).map_err(|e| {
724				crate::error::QuantusError::Generic(format!("Invalid account address: {e:?}"))
725			})?;
726			let account_bytes: [u8; 32] = *account_sp.as_ref();
727			let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
728			let storage_addr = quantus_subxt::api::storage().recovery().recoverable(account_id);
729			let latest = quantus_client.get_latest_block().await?;
730			let value = quantus_client
731				.client()
732				.storage()
733				.at(latest)
734				.fetch(&storage_addr)
735				.await
736				.map_err(|e| {
737					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
738				})?;
739			if let Some(cfg) = value {
740				log_print!(
741					"{}",
742					serde_json::json!({
743						"delay_period": cfg.delay_period,
744						"deposit": cfg.deposit,
745						"friends": cfg.friends.0.iter().map(|f| format!("{f}")).collect::<Vec<_>>(),
746						"threshold": cfg.threshold,
747					})
748				);
749			} else {
750				log_print!("{}", serde_json::json!({"recoverable": false}));
751			}
752			Ok(())
753		},
754	}
755}