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_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 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 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 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}