quantus_cli/cli/
system.rs

1//! `quantus system` subcommand - system information
2use crate::{
3	chain::client::{ChainConfig, QuantusClient},
4	log_print, log_verbose,
5};
6use colored::Colorize;
7use serde_json::Value;
8use subxt::OnlineClient;
9
10// Import ChainHead RPC API
11use std::error::Error;
12use subxt::{
13	backend::{chain_head::ChainHeadRpcMethods, rpc::RpcClient},
14	PolkadotConfig,
15};
16
17/// Chain native token information structure
18#[derive(Debug, Clone)]
19pub struct TokenInfo {
20	pub symbol: String,
21	pub decimals: u8,
22	pub ss58_format: Option<u8>,
23}
24
25/// Chain information from ChainHead API
26#[derive(Debug, Clone)]
27pub struct ChainInfo {
28	pub token: TokenInfo,
29	pub chain_name: Option<String>,
30	pub genesis_hash: Option<String>,
31}
32
33/// Client for retrieving token information using ChainHead RPC
34pub struct ChainHeadTokenClient {
35	rpc: ChainHeadRpcMethods<PolkadotConfig>,
36}
37
38impl ChainHeadTokenClient {
39	/// Creates a new client from endpoint URL
40	pub async fn new(url: &str) -> Result<Self, Box<dyn Error>> {
41		let rpc_client = RpcClient::from_url(url).await?;
42		let rpc = ChainHeadRpcMethods::<PolkadotConfig>::new(rpc_client);
43
44		Ok(Self { rpc })
45	}
46
47	/// Gets native chain token information using ChainHead RPC
48	pub async fn get_token_info(&self) -> Result<TokenInfo, Box<dyn Error>> {
49		// Get system properties using chainspec_v1_properties
50		let properties: serde_json::Map<String, Value> = self.rpc.chainspec_v1_properties().await?;
51
52		// Extract token symbol
53		let symbol = properties
54			.get("tokenSymbol")
55			.and_then(|v| v.as_str())
56			.unwrap_or("UNIT") // default to UNIT if no information
57			.to_string();
58
59		// Extract decimal places
60		let decimals = properties.get("tokenDecimals").and_then(|v| v.as_u64()).unwrap_or(0) as u8; // default to 0 if no information
61
62		// Extract SS58 format (optional)
63		let ss58_format = properties.get("ss58Format").and_then(|v| v.as_u64()).map(|v| v as u8);
64
65		Ok(TokenInfo { symbol, decimals, ss58_format })
66	}
67
68	/// Gets chain name
69	pub async fn get_chain_name(&self) -> Result<String, Box<dyn Error>> {
70		Ok(self.rpc.chainspec_v1_chain_name().await?)
71	}
72
73	/// Gets genesis hash
74	pub async fn get_genesis_hash(&self) -> Result<String, Box<dyn Error>> {
75		let hash = self.rpc.chainspec_v1_genesis_hash().await?;
76		Ok(format!("{hash:?}")) // Format the hash for display
77	}
78}
79
80/// Gets complete chain information using ChainHead API
81pub async fn get_complete_chain_info(node_url: &str) -> crate::error::Result<ChainInfo> {
82	match ChainHeadTokenClient::new(node_url).await {
83		Ok(client) => {
84			let token_info = client.get_token_info().await.map_err(|e| {
85				crate::error::QuantusError::NetworkError(format!(
86					"ChainHead token info failed: {e:?}"
87				))
88			})?;
89
90			let chain_name = client.get_chain_name().await.ok();
91			let genesis_hash = client.get_genesis_hash().await.ok();
92
93			Ok(ChainInfo { token: token_info, chain_name, genesis_hash })
94		},
95		Err(e) => {
96			log_verbose!("❌ ChainHead client creation failed: {:?}", e);
97			Err(crate::error::QuantusError::NetworkError(format!("ChainHead client failed: {e:?}")))
98		},
99	}
100}
101
102/// Get system information including ChainHead data
103pub async fn get_system_info(quantus_client: &QuantusClient) -> crate::error::Result<()> {
104	log_verbose!("🔍 Querying system information...");
105
106	// Get complete chain information from ChainHead API using the actual node_url
107	let chain_info = get_complete_chain_info(quantus_client.node_url()).await?;
108
109	// Get metadata information
110	let metadata = quantus_client.client().metadata();
111	let pallets: Vec<_> = metadata.pallets().collect();
112
113	log_print!("🏗️  Chain System Information:");
114	log_print!(
115		"   💰 Token: {} ({} decimals)",
116		chain_info.token.symbol.bright_yellow(),
117		chain_info.token.decimals.to_string().bright_cyan()
118	);
119
120	if let Some(ss58_format) = chain_info.token.ss58_format {
121		log_print!("   🔢 SS58 Format: {}", ss58_format.to_string().bright_magenta());
122	}
123
124	if let Some(name) = &chain_info.chain_name {
125		log_print!("   🔗 Chain: {}", name.bright_green());
126	}
127
128	if let Some(hash) = &chain_info.genesis_hash {
129		log_print!("   🧬 Genesis: {}...", hash[..16].bright_cyan());
130	}
131
132	log_print!("   📦 Pallets: {}", pallets.len().to_string());
133	log_print!("   🔧 Runtime: Substrate-based");
134
135	log_verbose!("💡 Use 'quantus metadata' to explore all available pallets and calls");
136
137	log_verbose!("✅ System info retrieved successfully!");
138
139	Ok(())
140}
141
142/// Get detailed chain parameters information
143pub async fn get_detailed_chain_params(
144	quantus_client: &crate::chain::client::QuantusClient,
145	show_raw_data: bool,
146) -> crate::error::Result<()> {
147	log_print!("🔧 Runtime Information:");
148
149	// Get genesis hash
150	let genesis_hash = quantus_client.get_genesis_hash().await?;
151	log_print!("   🧬 Genesis hash: {}", genesis_hash.to_string().bright_cyan());
152
153	// Get runtime version
154	let (spec_version, transaction_version) = quantus_client.get_runtime_version().await?;
155	log_print!("   📋 Spec version: {}", spec_version.to_string().bright_green());
156	log_print!("   🔄 Transaction version: {}", transaction_version.to_string().bright_yellow());
157
158	// Get current block info
159	use jsonrpsee::core::client::ClientT;
160	let current_block: serde_json::Value = quantus_client
161		.rpc_client()
162		.request::<serde_json::Value, [(); 0]>("chain_getHeader", [])
163		.await
164		.map_err(|e| {
165			crate::error::QuantusError::NetworkError(format!(
166				"Failed to fetch current block: {e:?}"
167			))
168		})?;
169
170	if let Some(block_number_str) = current_block["number"].as_str() {
171		if let Ok(block_number) = u64::from_str_radix(&block_number_str[2..], 16) {
172			log_print!("   📦 Current block: {}", block_number.to_string().bright_blue());
173
174			// Calculate era based on block number
175			let period = 64u64;
176			let phase = block_number % period;
177			log_print!("   ⏰ Era: period={}, phase={}", period, phase);
178			log_print!(
179				"   💡 Transaction era: Era::Mortal({}, {}) or Era::Immortal",
180				period,
181				phase
182			);
183		}
184	}
185
186	// Get full runtime info
187	let runtime_info: serde_json::Value = quantus_client
188		.rpc_client()
189		.request::<serde_json::Value, [(); 0]>("state_getRuntimeVersion", [])
190		.await
191		.map_err(|e| {
192			crate::error::QuantusError::NetworkError(format!("Failed to fetch runtime info: {e:?}"))
193		})?;
194
195	if show_raw_data {
196		log_verbose!("📋 Full runtime info: {:?}", runtime_info);
197	}
198
199	log_print!("📋 Runtime Details:");
200	log_print!(
201		"   🏷️  Spec name: {}",
202		runtime_info["specName"].as_str().unwrap_or("unknown").bright_magenta()
203	);
204	log_print!(
205		"   🔧 Implementation name: {}",
206		runtime_info["implName"].as_str().unwrap_or("unknown").bright_cyan()
207	);
208	log_print!(
209		"   📦 Implementation version: {}",
210		runtime_info["implVersion"].as_u64().unwrap_or(0).to_string().bright_yellow()
211	);
212	log_print!(
213		"   ✍️  Authoring version: {}",
214		runtime_info["authoringVersion"].as_u64().unwrap_or(0).to_string().bright_blue()
215	);
216	log_print!(
217		"   🗂️  State version: {}",
218		runtime_info["stateVersion"].as_u64().unwrap_or(0).to_string().bright_green()
219	);
220	log_print!(
221		"   💻 System version: {}",
222		runtime_info["systemVersion"].as_u64().unwrap_or(0).to_string().bright_red()
223	);
224
225	// Get chain properties
226	let chain_props: serde_json::Value = quantus_client
227		.rpc_client()
228		.request::<serde_json::Value, [(); 0]>("system_properties", [])
229		.await
230		.map_err(|e| {
231			crate::error::QuantusError::NetworkError(format!(
232				"Failed to fetch chain properties: {e:?}"
233			))
234		})?;
235
236	if show_raw_data {
237		log_verbose!("🔗 Chain properties: {:?}", chain_props);
238	}
239
240	if show_raw_data {
241		log_verbose!("📦 Current block: {:?}", current_block);
242	}
243
244	// Show block details
245	log_print!("📦 Block Details:");
246	if let Some(parent_hash) = current_block["parentHash"].as_str() {
247		log_print!("   🔗 Parent hash: {}...", parent_hash[..16].bright_cyan());
248	}
249	if let Some(state_root) = current_block["stateRoot"].as_str() {
250		log_print!("   🗂️  State root: {}...", state_root[..16].bright_magenta());
251	}
252	if let Some(extrinsics_root) = current_block["extrinsicsRoot"].as_str() {
253		log_print!("   📄 Extrinsics root: {}...", extrinsics_root[..16].bright_yellow());
254	}
255
256	Ok(())
257}
258
259/// Get detailed metadata statistics
260pub async fn get_metadata_stats(client: &OnlineClient<ChainConfig>) -> crate::error::Result<()> {
261	log_verbose!("🔍 Getting metadata statistics...");
262
263	let metadata = client.metadata();
264	let pallets: Vec<_> = metadata.pallets().collect();
265
266	log_verbose!("🔍 SubXT metadata: {} pallets available", pallets.len());
267
268	log_print!("📊 Metadata Statistics:");
269	log_print!("   📦 Total pallets: {}", pallets.len());
270	log_print!("   🔗 Metadata version: SubXT (type-safe)");
271
272	// Count calls across all pallets
273	let mut total_calls = 0;
274	for pallet in &pallets {
275		if let Some(calls) = pallet.call_variants() {
276			total_calls += calls.len();
277		}
278	}
279
280	log_print!("   🎯 Total calls: {}", total_calls);
281	log_print!("   ⚡ API: Type-safe SubXT");
282
283	Ok(())
284}
285
286/// Handle system command
287pub async fn handle_system_command(node_url: &str) -> crate::error::Result<()> {
288	log_print!("🚀 System Information");
289	let quantus_client = QuantusClient::new(node_url).await?;
290	get_system_info(&quantus_client).await?;
291
292	Ok(())
293}
294
295/// Handle extended system commands with additional info
296pub async fn handle_system_extended_command(
297	node_url: &str,
298	show_runtime: bool,
299	show_metadata: bool,
300	show_rpc_methods: bool,
301	verbose: bool,
302) -> crate::error::Result<()> {
303	log_print!("🚀 Extended System Information");
304
305	let quantus_client = QuantusClient::new(node_url).await?;
306
307	// Basic system info
308	get_system_info(&quantus_client).await?;
309
310	if show_runtime {
311		log_print!("");
312		get_detailed_chain_params(&quantus_client, verbose).await?;
313	}
314
315	if show_metadata {
316		log_print!("");
317		get_metadata_stats(quantus_client.client()).await?;
318	}
319
320	if show_rpc_methods {
321		log_print!("");
322		list_rpc_methods(&quantus_client).await?;
323	}
324
325	Ok(())
326}
327
328/// List all available JSON-RPC methods from the connected node
329async fn list_rpc_methods(quantus_client: &QuantusClient) -> crate::error::Result<()> {
330	use jsonrpsee::core::client::ClientT;
331
332	log_print!("🧭 JSON-RPC Methods exposed by node:");
333
334	let response: serde_json::Value = quantus_client
335		.rpc_client()
336		.request::<serde_json::Value, [(); 0]>("rpc_methods", [])
337		.await
338		.map_err(|e| {
339			crate::error::QuantusError::NetworkError(format!("Failed to fetch rpc_methods: {e:?}"))
340		})?;
341
342	if let Some(methods) = response.get("methods").and_then(|v| v.as_array()) {
343		for method in methods {
344			if let Some(name) = method.as_str() {
345				log_print!("\t{}", name);
346			}
347		}
348		if let Some(version) = response.get("version").and_then(|v| v.as_u64()) {
349			log_verbose!("RPC methods schema version: {}", version);
350		}
351	} else if let Some(array) = response.as_array() {
352		for method in array {
353			if let Some(name) = method.as_str() {
354				log_print!("\t{}", name);
355			}
356		}
357	} else {
358		log_print!("   (no methods returned or unexpected format)");
359		log_verbose!("rpc_methods raw response: {:?}", response);
360	}
361
362	Ok(())
363}