quantus_cli/cli/
high_security.rs

1use crate::{
2	chain::quantus_subxt, cli::address_format::QuantusSS58, log_error, log_print, log_success,
3	log_verbose,
4};
5use clap::Subcommand;
6use colored::Colorize;
7use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
8
9/// High-Security (Reversible) commands
10#[derive(Subcommand, Debug)]
11pub enum HighSecurityCommands {
12	/// Check High-Security status for an account
13	Status {
14		/// Account address to check (SS58 or wallet name)
15		#[arg(long)]
16		account: String,
17	},
18
19	/// Set High-Security (reversibility) for an account
20	Set {
21		/// Interceptor account (SS58 or wallet name)
22		#[arg(long)]
23		interceptor: String,
24
25		/// Delay in blocks (mutually exclusive with --delay-seconds)
26		#[arg(long, conflicts_with = "delay_seconds")]
27		delay_blocks: Option<u32>,
28
29		/// Delay in seconds (mutually exclusive with --delay-blocks)
30		#[arg(long, conflicts_with = "delay_blocks")]
31		delay_seconds: Option<u64>,
32
33		/// Wallet name to sign with
34		#[arg(short, long)]
35		from: String,
36
37		/// Password for the wallet
38		#[arg(short, long)]
39		password: Option<String>,
40
41		/// Read password from file (for scripting)
42		#[arg(long)]
43		password_file: Option<String>,
44	},
45}
46
47/// Handle high security commands
48pub async fn handle_high_security_command(
49	command: HighSecurityCommands,
50	node_url: &str,
51	finalized: bool,
52) -> crate::error::Result<()> {
53	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
54
55	match command {
56		HighSecurityCommands::Status { account } => {
57			log_print!("🔍 Checking High Security Status");
58
59			// Resolve account address
60			let resolved_account = crate::cli::common::resolve_address(&account)?;
61			let account_id_sp = SpAccountId32::from_ss58check(&resolved_account).map_err(|e| {
62				crate::error::QuantusError::Generic(format!(
63					"Invalid account address '{resolved_account}': {e:?}"
64				))
65			})?;
66			let account_id_bytes: [u8; 32] = *account_id_sp.as_ref();
67			let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_id_bytes);
68
69			// Convert account to Quantus SS58 format
70			let account_ss58 = account_id.to_quantus_ss58();
71
72			// Query storage
73			let storage_addr = quantus_subxt::api::storage()
74				.reversible_transfers()
75				.high_security_accounts(account_id);
76			let latest = quantus_client.get_latest_block().await?;
77			let value = quantus_client
78				.client()
79				.storage()
80				.at(latest)
81				.fetch(&storage_addr)
82				.await
83				.map_err(|e| {
84					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
85				})?;
86
87			log_print!("📋 Account: {}", account_ss58.bright_cyan());
88
89			if let Some(high_security_data) = value {
90				log_success!("✅ High Security: ENABLED");
91
92				// Convert interceptor to Quantus SS58 format
93				let interceptor_ss58 = high_security_data.interceptor.to_quantus_ss58();
94
95				log_print!("🛡️  Guardian/Interceptor: {}", interceptor_ss58.bright_green());
96
97				// Format delay display
98				match high_security_data.delay {
99					quantus_subxt::api::runtime_types::qp_scheduler::BlockNumberOrTimestamp::BlockNumber(blocks) => {
100						log_print!("⏱️  Delay: {} blocks", blocks.to_string().bright_yellow());
101					},
102					quantus_subxt::api::runtime_types::qp_scheduler::BlockNumberOrTimestamp::Timestamp(ms) => {
103						let seconds = ms / 1000;
104						log_print!("⏱️  Delay: {} seconds", seconds.to_string().bright_yellow());
105					},
106				}
107			} else {
108				log_print!("❌ High Security: DISABLED");
109				log_print!("💡 This account does not have high-security reversibility enabled.");
110			}
111
112			Ok(())
113		},
114
115		HighSecurityCommands::Set {
116			interceptor,
117			delay_blocks,
118			delay_seconds,
119			from,
120			password,
121			password_file,
122		} => {
123			log_print!("🛡️  Set High Security");
124			log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
125			let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
126
127			// Resolve interceptor: allow wallet name or SS58 address
128			let interceptor_resolved = crate::cli::common::resolve_address(&interceptor)?;
129			let interceptor_sp =
130				SpAccountId32::from_ss58check(&interceptor_resolved).map_err(|e| {
131					crate::error::QuantusError::Generic(format!(
132						"Invalid interceptor address '{interceptor_resolved}': {e:?}"
133					))
134				})?;
135			let interceptor_bytes: [u8; 32] = *interceptor_sp.as_ref();
136			let interceptor_subxt =
137				subxt::ext::subxt_core::utils::AccountId32::from(interceptor_bytes);
138
139			// Build delay enum for set_high_security
140			use quantus_subxt::api::reversible_transfers::calls::types::set_high_security::Delay as HsDelay;
141			let delay_value = match (delay_blocks, delay_seconds) {
142				(Some(blocks), None) => HsDelay::BlockNumber(blocks),
143				(None, Some(seconds)) => HsDelay::Timestamp(seconds * 1000), /* Convert seconds */
144				// to milliseconds
145				(None, None) => {
146					log_error!("❌ You must specify either --delay-blocks or --delay-seconds");
147					return Err(crate::error::QuantusError::Generic(
148						"Missing delay parameter".to_string(),
149					));
150				},
151				(Some(_), Some(_)) => {
152					unreachable!("clap conflicts_with ensures these are mutually exclusive")
153				},
154			};
155
156			log_verbose!("✍️  Creating set_high_security extrinsic...");
157
158			// Current generated metadata expects (delay, interceptor).
159			let tx_call = quantus_subxt::api::tx()
160				.reversible_transfers()
161				.set_high_security(delay_value, interceptor_subxt);
162
163			let tx_hash = crate::cli::common::submit_transaction(
164				&quantus_client,
165				&keypair,
166				tx_call,
167				None,
168				finalized,
169			)
170			.await?;
171
172			log_success!("✅ SUCCESS High security set! Hash: 0x{}", hex::encode(tx_hash.as_ref()));
173
174			Ok(())
175		},
176	}
177}