quantus_cli/cli/
system.rs1use crate::{
3 chain::client::{ChainConfig, QuantusClient},
4 log_print, log_verbose,
5};
6use colored::Colorize;
7use serde_json::Value;
8use subxt::OnlineClient;
9
10use std::error::Error;
12use subxt::{
13 backend::{chain_head::ChainHeadRpcMethods, rpc::RpcClient},
14 PolkadotConfig,
15};
16
17#[derive(Debug, Clone)]
19pub struct TokenInfo {
20 pub symbol: String,
21 pub decimals: u8,
22 pub ss58_format: Option<u8>,
23}
24
25#[derive(Debug, Clone)]
27pub struct ChainInfo {
28 pub token: TokenInfo,
29 pub chain_name: Option<String>,
30 pub genesis_hash: Option<String>,
31}
32
33pub struct ChainHeadTokenClient {
35 rpc: ChainHeadRpcMethods<PolkadotConfig>,
36}
37
38impl ChainHeadTokenClient {
39 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 pub async fn get_token_info(&self) -> Result<TokenInfo, Box<dyn Error>> {
49 let properties: serde_json::Map<String, Value> = self.rpc.chainspec_v1_properties().await?;
51
52 let symbol = properties
54 .get("tokenSymbol")
55 .and_then(|v| v.as_str())
56 .unwrap_or("UNIT") .to_string();
58
59 let decimals = properties.get("tokenDecimals").and_then(|v| v.as_u64()).unwrap_or(0) as u8; 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 pub async fn get_chain_name(&self) -> Result<String, Box<dyn Error>> {
70 Ok(self.rpc.chainspec_v1_chain_name().await?)
71 }
72
73 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:?}")) }
78}
79
80pub 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
102pub async fn get_system_info(quantus_client: &QuantusClient) -> crate::error::Result<()> {
104 log_verbose!("🔍 Querying system information...");
105
106 let chain_info = get_complete_chain_info(quantus_client.node_url()).await?;
108
109 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
142pub 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 let genesis_hash = quantus_client.get_genesis_hash().await?;
151 log_print!(" 🧬 Genesis hash: {}", genesis_hash.to_string().bright_cyan());
152
153 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 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 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 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 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 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
259pub 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 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
286pub 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
295pub 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 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
328async 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}