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