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	finalized: bool,
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_with_finalization(
194				&quantus_client,
195				&rescuer_key,
196				call,
197				None,
198				finalized,
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_with_finalization(
229				&quantus_client,
230				&friend_key,
231				call,
232				None,
233				finalized,
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 =
257				crate::cli::common::submit_transaction(&quantus_client, &rescuer_key, call, None)
258					.await
259					.map_err(|e| {
260						crate::error::QuantusError::NetworkError(format!(
261							"Failed to submit claim_recovery transaction: {e}"
262						))
263					})?;
264
265			log_success!("✅ Claim submitted successfully {:?}", tx_hash);
266		},
267
268		RecoveryCommands::RecoverAll {
269			rescuer,
270			lost,
271			dest,
272			keep_alive,
273			password,
274			password_file,
275		} => {
276			use quantus_subxt::api::runtime_types::pallet_balances::pallet::Call as BalancesCall;
277
278			let rescuer_key =
279				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
280			let rescuer_addr = rescuer_key.to_account_id_ss58check();
281			log_print!("🔑 Rescuer: {}", rescuer);
282			log_print!("🔑 Rescuer address: {}", rescuer_addr);
283
284			let lost_resolved = resolve_address(&lost)?;
285			let dest_resolved = resolve_address(&dest)?;
286			log_print!("🆘 Lost input: {} -> {}", lost, lost_resolved);
287			log_print!("🎯 Dest input: {} -> {}", dest, dest_resolved);
288			log_print!("🛟 keep_alive: {}", keep_alive);
289
290			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
291				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
292			})?;
293			let dest_sp = SpAccountId32::from_ss58check(&dest_resolved).map_err(|e| {
294				crate::error::QuantusError::Generic(format!("Invalid dest address: {e:?}"))
295			})?;
296
297			let lost_id_bytes: [u8; 32] = *lost_sp.as_ref();
298			let dest_id_bytes: [u8; 32] = *dest_sp.as_ref();
299			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_id_bytes);
300			let dest_id = subxt::ext::subxt_core::utils::AccountId32::from(dest_id_bytes);
301
302			// Check proxy mapping for rescuer
303			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_addr).map_err(|e| {
304				crate::error::QuantusError::Generic(format!(
305					"Invalid rescuer address from wallet: {e:?}"
306				))
307			})?;
308			let rescuer_id_bytes: [u8; 32] = *rescuer_sp.as_ref();
309			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_id_bytes);
310			let proxy_storage = quantus_subxt::api::storage().recovery().proxy(rescuer_id);
311			let latest = quantus_client.get_latest_block().await?;
312			let proxy_result =
313				quantus_client.client().storage().at(latest).fetch(&proxy_storage).await;
314			let proxy_of = match proxy_result {
315				Ok(Some(proxy)) => {
316					log_print!("🧩 Proxy mapping: rescuer proxies -> {}", format!("{}", proxy));
317					Some(proxy)
318				},
319				Ok(None) => {
320					log_error!(
321						"❌ No proxy mapping found for rescuer - recovery not set up properly"
322					);
323					return Err(crate::error::QuantusError::Generic(
324						"Rescuer has no proxy mapping. Recovery process may not be properly set up.".to_string()
325					));
326				},
327				Err(e) => {
328					log_error!("❌ Proxy mapping fetch error: {:?}", e);
329					return Err(crate::error::QuantusError::NetworkError(format!(
330						"Failed to check proxy mapping: {e:?}"
331					)));
332				},
333			};
334
335			// Validate that the proxy points to the correct lost account
336			if let Some(proxy) = proxy_of {
337				let proxy_addr = format!("{proxy}");
338				if proxy_addr != lost_resolved {
339					log_error!(
340						"❌ Proxy mismatch! Rescuer proxies {} but we're trying to recover {}",
341						proxy_addr,
342						lost_resolved
343					);
344					return Err(crate::error::QuantusError::Generic(format!(
345						"Proxy mismatch: rescuer proxies {proxy_addr} but target is {lost_resolved}"
346					)));
347				}
348				log_print!("✅ Proxy validation successful");
349			}
350
351			let inner_call = quantus_subxt::api::Call::Balances(BalancesCall::transfer_all {
352				dest: subxt::ext::subxt_core::utils::MultiAddress::Id(dest_id),
353				keep_alive,
354			});
355			log_print!("🧱 Inner call: Balances.transfer_all(keep_alive={})", keep_alive);
356
357			let call = quantus_subxt::api::tx()
358				.recovery()
359				.as_recovered(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id), inner_call);
360
361			let tx_hash = match crate::cli::common::submit_transaction_with_finalization(
362				&quantus_client,
363				&rescuer_key,
364				call,
365				None,
366				finalized,
367			)
368			.await
369			{
370				Ok(h) => h,
371				Err(e) => {
372					log_error!("❌ Submit error (recover_all): {:?}", e);
373					return Err(e);
374				},
375			};
376			log_success!("✅ recover_all submitted successfully {:?}", tx_hash);
377		},
378
379		RecoveryCommands::RecoverAmount {
380			rescuer,
381			lost,
382			dest,
383			amount_quan,
384			keep_alive,
385			password,
386			password_file,
387		} => {
388			use quantus_subxt::api::runtime_types::pallet_balances::pallet::Call as BalancesCall;
389
390			let rescuer_key =
391				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
392
393			let rescuer_addr = rescuer_key.to_account_id_ss58check();
394			log_print!("🔑 Rescuer: {}", rescuer);
395			log_print!("🔑 Rescuer address: {}", rescuer_addr);
396
397			let lost_resolved = resolve_address(&lost)?;
398			let dest_resolved = resolve_address(&dest)?;
399			log_print!("🆘 Lost input: {} -> {}", lost, lost_resolved);
400			log_print!("🎯 Dest input: {} -> {}", dest, dest_resolved);
401			log_print!("💵 amount_quan: {} (QUAN_DECIMALS={})", amount_quan, QUAN_DECIMALS);
402			log_print!("🛟 keep_alive: {}", keep_alive);
403
404			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
405				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
406			})?;
407			let dest_sp = SpAccountId32::from_ss58check(&dest_resolved).map_err(|e| {
408				crate::error::QuantusError::Generic(format!("Invalid dest address: {e:?}"))
409			})?;
410
411			let lost_id_bytes: [u8; 32] = *lost_sp.as_ref();
412			let dest_id_bytes: [u8; 32] = *dest_sp.as_ref();
413			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_id_bytes);
414			let dest_id = subxt::ext::subxt_core::utils::AccountId32::from(dest_id_bytes);
415
416			let amount_plancks = amount_quan.saturating_mul(QUAN_DECIMALS);
417			log_print!("💵 amount_plancks: {}", amount_plancks);
418
419			let latest = quantus_client.get_latest_block().await?;
420
421			// Check account balance before attempting transfer
422			log_print!("💰 Checking lost account balance...");
423			let balance_result = quantus_client
424				.client()
425				.storage()
426				.at(latest)
427				.fetch(&quantus_subxt::api::storage().system().account(lost_id.clone()))
428				.await;
429
430			let account_info = match balance_result {
431				Ok(Some(info)) => info,
432				Ok(None) => {
433					log_error!("❌ Lost account not found in storage");
434					return Err(crate::error::QuantusError::Generic(
435						"Lost account not found in storage".to_string(),
436					));
437				},
438				Err(e) => {
439					log_error!("❌ Failed to fetch account balance: {:?}", e);
440					return Err(crate::error::QuantusError::NetworkError(format!(
441						"Failed to fetch account balance: {e:?}"
442					)));
443				},
444			};
445
446			let available_balance = account_info.data.free;
447			log_print!("💰 Available balance: {} plancks", available_balance);
448
449			if available_balance < amount_plancks {
450				log_error!(
451					"❌ Insufficient funds! Account has {} plancks but needs {} plancks",
452					available_balance,
453					amount_plancks
454				);
455				return Err(crate::error::QuantusError::Generic(format!(
456					"Insufficient funds: account has {available_balance} plancks but transfer requires {amount_plancks} plancks"
457				)));
458			}
459
460			log_print!("✅ Balance validation successful - sufficient funds available");
461
462			let inner_call =
463				quantus_subxt::api::Call::Balances(BalancesCall::transfer_keep_alive {
464					dest: subxt::ext::subxt_core::utils::MultiAddress::Id(dest_id),
465					value: amount_plancks,
466				});
467
468			let call = quantus_subxt::api::tx()
469				.recovery()
470				.as_recovered(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id), inner_call);
471
472			let tx_hash = match crate::cli::common::submit_transaction_with_finalization(
473				&quantus_client,
474				&rescuer_key,
475				call,
476				None,
477				finalized,
478			)
479			.await
480			{
481				Ok(h) => h,
482				Err(e) => {
483					log_error!("❌ Submit error (recover_amount): {:?}", e);
484					return Err(e);
485				},
486			};
487			log_success!("✅ recover_amount submitted successfully {:?}", tx_hash);
488		},
489
490		RecoveryCommands::Close { lost, rescuer, password, password_file } => {
491			let lost_key = crate::wallet::load_keypair_from_wallet(&lost, password, password_file)?;
492			let rescuer_resolved = resolve_address(&rescuer)?;
493			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
494				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
495			})?;
496			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
497			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
498			let call = quantus_subxt::api::tx()
499				.recovery()
500				.close_recovery(subxt::ext::subxt_core::utils::MultiAddress::Id(rescuer_id));
501			let tx_hash = crate::cli::common::submit_transaction_with_finalization(
502				&quantus_client,
503				&lost_key,
504				call,
505				None,
506				finalized,
507			)
508			.await
509			.map_err(|e| {
510				crate::error::QuantusError::NetworkError(format!(
511					"Failed to submit close_recovery transaction: {e}"
512				))
513			})?;
514
515			log_print!("📋 Transaction submitted: 0x{}", hex::encode(tx_hash.as_ref()));
516			log_success!("✅ close_recovery submitted successfully");
517		},
518
519		RecoveryCommands::CancelProxy { rescuer, lost, password, password_file } => {
520			let rescuer_key =
521				crate::wallet::load_keypair_from_wallet(&rescuer, password, password_file)?;
522			let lost_resolved = resolve_address(&lost)?;
523			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
524				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
525			})?;
526			let lost_bytes: [u8; 32] = *lost_sp.as_ref();
527			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_bytes);
528			let call = quantus_subxt::api::tx()
529				.recovery()
530				.cancel_recovered(subxt::ext::subxt_core::utils::MultiAddress::Id(lost_id));
531			let tx_hash = crate::cli::common::submit_transaction_with_finalization(
532				&quantus_client,
533				&rescuer_key,
534				call,
535				None,
536				finalized,
537			)
538			.await
539			.map_err(|e| {
540				crate::error::QuantusError::NetworkError(format!(
541					"Failed to submit cancel_recovered transaction: {e}"
542				))
543			})?;
544
545			log_success!("✅ cancel_recovered submitted successfully {:?}", tx_hash);
546		},
547
548		RecoveryCommands::Active { lost, rescuer } => {
549			let lost_resolved = resolve_address(&lost)?;
550			let rescuer_resolved = resolve_address(&rescuer)?;
551			let lost_sp = SpAccountId32::from_ss58check(&lost_resolved).map_err(|e| {
552				crate::error::QuantusError::Generic(format!("Invalid lost address: {e:?}"))
553			})?;
554			let lost_bytes: [u8; 32] = *lost_sp.as_ref();
555			let lost_id = subxt::ext::subxt_core::utils::AccountId32::from(lost_bytes);
556			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
557				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
558			})?;
559			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
560			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
561			let storage_addr =
562				quantus_subxt::api::storage().recovery().active_recoveries(lost_id, rescuer_id);
563			let latest = quantus_client.get_latest_block().await?;
564			let value = quantus_client
565				.client()
566				.storage()
567				.at(latest)
568				.fetch(&storage_addr)
569				.await
570				.map_err(|e| {
571					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
572				})?;
573			if let Some(active) = value {
574				log_print!(
575					"{}",
576					serde_json::json!({
577						"created": active.created,
578						"deposit": active.deposit,
579						"friends_vouched": active.friends.0.len(),
580					})
581				);
582			} else {
583				log_print!("{}", serde_json::json!({"active": false}));
584			}
585		},
586
587		RecoveryCommands::ProxyOf { rescuer } => {
588			let rescuer_resolved = resolve_address(&rescuer)?;
589			let rescuer_sp = SpAccountId32::from_ss58check(&rescuer_resolved).map_err(|e| {
590				crate::error::QuantusError::Generic(format!("Invalid rescuer address: {e:?}"))
591			})?;
592			let rescuer_bytes: [u8; 32] = *rescuer_sp.as_ref();
593			let rescuer_id = subxt::ext::subxt_core::utils::AccountId32::from(rescuer_bytes);
594			let storage_addr = quantus_subxt::api::storage().recovery().proxy(rescuer_id);
595			let latest = quantus_client.get_latest_block().await?;
596			let value = quantus_client
597				.client()
598				.storage()
599				.at(latest)
600				.fetch(&storage_addr)
601				.await
602				.map_err(|e| {
603					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
604				})?;
605			if let Some(lost_id) = value {
606				log_print!("{}", serde_json::json!({"lost": format!("{}", lost_id)}));
607			} else {
608				log_print!("{}", serde_json::json!({"lost": null}));
609			}
610		},
611
612		RecoveryCommands::Config { account } => {
613			let account_resolved = resolve_address(&account)?;
614			let account_sp = SpAccountId32::from_ss58check(&account_resolved).map_err(|e| {
615				crate::error::QuantusError::Generic(format!("Invalid account address: {e:?}"))
616			})?;
617			let account_bytes: [u8; 32] = *account_sp.as_ref();
618			let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
619			let storage_addr = quantus_subxt::api::storage().recovery().recoverable(account_id);
620			let latest = quantus_client.get_latest_block().await?;
621			let value = quantus_client
622				.client()
623				.storage()
624				.at(latest)
625				.fetch(&storage_addr)
626				.await
627				.map_err(|e| {
628					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
629				})?;
630			if let Some(cfg) = value {
631				log_print!(
632					"{}",
633					serde_json::json!({
634						"delay_period": cfg.delay_period,
635						"deposit": cfg.deposit,
636						"friends": cfg.friends.0.iter().map(|f| format!("{f}")).collect::<Vec<_>>(),
637						"threshold": cfg.threshold,
638					})
639				);
640			} else {
641				log_print!("{}", serde_json::json!({"recoverable": false}));
642			}
643		},
644	};
645
646	Ok(())
647}