1use crate::{
2 chain::quantus_subxt, cli::common::resolve_address, log_error, log_print, log_success,
3};
4use clap::Subcommand;
5use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
7
8const QUAN_DECIMALS: u128 = 1_000_000_000_000; #[derive(Subcommand, Debug)]
13pub enum RecoveryCommands {
14 Initiate {
16 #[arg(long)]
18 rescuer: String,
19 #[arg(long)]
21 lost: String,
22 #[arg(short, long)]
24 password: Option<String>,
25 #[arg(long)]
27 password_file: Option<String>,
28 },
29
30 Vouch {
32 #[arg(long)]
34 friend: String,
35 #[arg(long)]
37 lost: String,
38 #[arg(long)]
40 rescuer: String,
41 #[arg(short, long)]
43 password: Option<String>,
44 #[arg(long)]
46 password_file: Option<String>,
47 },
48
49 Claim {
51 #[arg(long)]
53 rescuer: String,
54 #[arg(long)]
56 lost: String,
57 #[arg(short, long)]
59 password: Option<String>,
60 #[arg(long)]
62 password_file: Option<String>,
63 },
64
65 Close {
67 #[arg(long)]
69 lost: String,
70 #[arg(long)]
72 rescuer: String,
73 #[arg(short, long)]
75 password: Option<String>,
76 #[arg(long)]
78 password_file: Option<String>,
79 },
80
81 CancelProxy {
83 #[arg(long)]
85 rescuer: String,
86 #[arg(long)]
88 lost: String,
89 #[arg(short, long)]
91 password: Option<String>,
92 #[arg(long)]
94 password_file: Option<String>,
95 },
96
97 Active {
99 #[arg(long)]
101 lost: String,
102 #[arg(long)]
104 rescuer: String,
105 },
106
107 ProxyOf {
109 #[arg(long)]
111 rescuer: String,
112 },
113
114 Config {
116 #[arg(long)]
118 account: String,
119 },
120
121 RecoverAll {
123 #[arg(long)]
125 rescuer: String,
126 #[arg(long)]
128 lost: String,
129 #[arg(long)]
131 dest: String,
132 #[arg(long, default_value_t = true)]
134 keep_alive: bool,
135 #[arg(short, long)]
137 password: Option<String>,
138 #[arg(long)]
140 password_file: Option<String>,
141 },
142
143 RecoverAmount {
145 #[arg(long)]
147 rescuer: String,
148 #[arg(long)]
150 lost: String,
151 #[arg(long)]
153 dest: String,
154 #[arg(long, value_name = "AMOUNT_QUAN")]
156 amount_quan: u128,
157 #[arg(long, default_value_t = true)]
159 keep_alive: bool,
160 #[arg(short, long)]
162 password: Option<String>,
163 #[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 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 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 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}