Skip to main content

quantus_cli/cli/
recovery.rs

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