Skip to main content

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	/// Show all accounts that this account is guardian for
47	Entrusted {
48		/// Guardian account address (SS58 or wallet name)
49		#[arg(short, long)]
50		from: String,
51	},
52}
53
54/// Handle high security commands
55pub async fn handle_high_security_command(
56	command: HighSecurityCommands,
57	node_url: &str,
58	execution_mode: crate::cli::common::ExecutionMode,
59) -> crate::error::Result<()> {
60	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
61
62	match command {
63		HighSecurityCommands::Status { account } => {
64			log_print!("🔍 Checking High Security Status");
65
66			// Resolve account address
67			let resolved_account = crate::cli::common::resolve_address(&account)?;
68			let account_id_sp = SpAccountId32::from_ss58check(&resolved_account).map_err(|e| {
69				crate::error::QuantusError::Generic(format!(
70					"Invalid account address '{resolved_account}': {e:?}"
71				))
72			})?;
73			let account_id_bytes: [u8; 32] = *account_id_sp.as_ref();
74			let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_id_bytes);
75
76			// Convert account to Quantus SS58 format
77			let account_ss58 = account_id.to_quantus_ss58();
78
79			// Query storage
80			let storage_addr = quantus_subxt::api::storage()
81				.reversible_transfers()
82				.high_security_accounts(account_id);
83			let latest = quantus_client.get_latest_block().await?;
84			let value = quantus_client
85				.client()
86				.storage()
87				.at(latest)
88				.fetch(&storage_addr)
89				.await
90				.map_err(|e| {
91					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
92				})?;
93
94			log_print!("📋 Account: {}", account_ss58.bright_cyan());
95
96			if let Some(high_security_data) = value {
97				log_success!("✅ High Security: ENABLED");
98
99				// Convert interceptor to Quantus SS58 format
100				let interceptor_ss58 = high_security_data.interceptor.to_quantus_ss58();
101
102				log_print!("🛡️  Guardian/Interceptor: {}", interceptor_ss58.bright_green());
103
104				// Format delay display
105				match high_security_data.delay {
106					quantus_subxt::api::runtime_types::qp_scheduler::BlockNumberOrTimestamp::BlockNumber(blocks) => {
107						log_print!("⏱️  Delay: {} blocks", blocks.to_string().bright_yellow());
108					},
109					quantus_subxt::api::runtime_types::qp_scheduler::BlockNumberOrTimestamp::Timestamp(ms) => {
110						let seconds = ms / 1000;
111						log_print!("⏱️  Delay: {} seconds", seconds.to_string().bright_yellow());
112					},
113				}
114			} else {
115				log_print!("❌ High Security: DISABLED");
116				log_print!("💡 This account does not have high-security reversibility enabled.");
117			}
118
119			Ok(())
120		},
121
122		HighSecurityCommands::Set {
123			interceptor,
124			delay_blocks,
125			delay_seconds,
126			from,
127			password,
128			password_file,
129		} => {
130			log_print!("🛡️  Set High Security");
131			log_verbose!("📦 Using wallet: {}", from.bright_blue().bold());
132			let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
133
134			// Resolve interceptor: allow wallet name or SS58 address
135			let interceptor_resolved = crate::cli::common::resolve_address(&interceptor)?;
136			let interceptor_sp =
137				SpAccountId32::from_ss58check(&interceptor_resolved).map_err(|e| {
138					crate::error::QuantusError::Generic(format!(
139						"Invalid interceptor address '{interceptor_resolved}': {e:?}"
140					))
141				})?;
142			let interceptor_bytes: [u8; 32] = *interceptor_sp.as_ref();
143			let interceptor_subxt =
144				subxt::ext::subxt_core::utils::AccountId32::from(interceptor_bytes);
145
146			// Build delay enum for set_high_security
147			use quantus_subxt::api::reversible_transfers::calls::types::set_high_security::Delay as HsDelay;
148			let delay_value = match (delay_blocks, delay_seconds) {
149				(Some(blocks), None) => HsDelay::BlockNumber(blocks),
150				(None, Some(seconds)) => HsDelay::Timestamp(seconds * 1000), /* Convert seconds */
151				// to milliseconds
152				(None, None) => {
153					log_error!("❌ You must specify either --delay-blocks or --delay-seconds");
154					return Err(crate::error::QuantusError::Generic(
155						"Missing delay parameter".to_string(),
156					));
157				},
158				(Some(_), Some(_)) => {
159					unreachable!("clap conflicts_with ensures these are mutually exclusive")
160				},
161			};
162
163			log_verbose!("✍️  Creating set_high_security extrinsic...");
164
165			// Current generated metadata expects (delay, interceptor).
166			let tx_call = quantus_subxt::api::tx()
167				.reversible_transfers()
168				.set_high_security(delay_value, interceptor_subxt);
169
170			let tx_hash = crate::cli::common::submit_transaction(
171				&quantus_client,
172				&keypair,
173				tx_call,
174				None,
175				execution_mode,
176			)
177			.await?;
178
179			log_success!("✅ SUCCESS High security set! Hash: 0x{}", hex::encode(tx_hash.as_ref()));
180
181			Ok(())
182		},
183
184		HighSecurityCommands::Entrusted { from } => {
185			log_print!("🔍 Checking entrusted accounts");
186
187			// Resolve guardian account address
188			let guardian_resolved = crate::cli::common::resolve_address(&from)?;
189			let guardian_sp = SpAccountId32::from_ss58check(&guardian_resolved).map_err(|e| {
190				crate::error::QuantusError::Generic(format!(
191					"Invalid guardian address '{guardian_resolved}': {e:?}"
192				))
193			})?;
194			let guardian_bytes: [u8; 32] = *guardian_sp.as_ref();
195			let guardian_account = subxt::ext::subxt_core::utils::AccountId32::from(guardian_bytes);
196
197			// Convert guardian to Quantus SS58 format
198			let guardian_ss58 = guardian_account.to_quantus_ss58();
199
200			// Query storage for entrusted accounts
201			let storage_addr = quantus_subxt::api::storage()
202				.reversible_transfers()
203				.interceptor_index(guardian_account);
204			let latest = quantus_client.get_latest_block().await?;
205			let value = quantus_client
206				.client()
207				.storage()
208				.at(latest)
209				.fetch(&storage_addr)
210				.await
211				.map_err(|e| {
212					crate::error::QuantusError::NetworkError(format!("Fetch error: {e:?}"))
213				})?;
214
215			log_print!("🛡️  Guardian: {}", guardian_ss58.bright_cyan());
216
217			if let Some(entrusted_accounts) = value {
218				if entrusted_accounts.0.is_empty() {
219					log_print!("📋 No entrusted accounts found.");
220				} else {
221					log_success!("✅ Found {} entrusted account(s):", entrusted_accounts.0.len());
222
223					for (index, account_id) in entrusted_accounts.0.iter().enumerate() {
224						let account_ss58 = account_id.to_quantus_ss58();
225						log_print!("  {}. {}", index + 1, account_ss58.bright_green());
226					}
227				}
228			} else {
229				log_print!("📋 No entrusted accounts found.");
230			}
231
232			Ok(())
233		},
234	}
235}