use crate::{
chain::client::{ChainConfig, QuantusClient},
log_print, log_verbose,
};
use colored::Colorize;
use serde_json::Value;
use subxt::OnlineClient;
use std::error::Error;
use subxt::{
backend::{chain_head::ChainHeadRpcMethods, rpc::RpcClient},
PolkadotConfig,
};
#[derive(Debug, Clone)]
pub struct TokenInfo {
pub symbol: String,
pub decimals: u8,
pub ss58_format: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct ChainInfo {
pub token: TokenInfo,
pub chain_name: Option<String>,
pub genesis_hash: Option<String>,
}
pub struct ChainHeadTokenClient {
rpc: ChainHeadRpcMethods<PolkadotConfig>,
}
impl ChainHeadTokenClient {
pub async fn new(url: &str) -> Result<Self, Box<dyn Error>> {
let rpc_client = RpcClient::from_url(url).await?;
let rpc = ChainHeadRpcMethods::<PolkadotConfig>::new(rpc_client);
Ok(Self { rpc })
}
pub async fn get_token_info(&self) -> Result<TokenInfo, Box<dyn Error>> {
let properties: serde_json::Map<String, Value> = self.rpc.chainspec_v1_properties().await?;
let symbol = properties
.get("tokenSymbol")
.and_then(|v| v.as_str())
.unwrap_or("UNIT") .to_string();
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);
Ok(TokenInfo { symbol, decimals, ss58_format })
}
pub async fn get_chain_name(&self) -> Result<String, Box<dyn Error>> {
Ok(self.rpc.chainspec_v1_chain_name().await?)
}
pub async fn get_genesis_hash(&self) -> Result<String, Box<dyn Error>> {
let hash = self.rpc.chainspec_v1_genesis_hash().await?;
Ok(format!("{hash:?}")) }
}
pub async fn get_complete_chain_info(node_url: &str) -> crate::error::Result<ChainInfo> {
match ChainHeadTokenClient::new(node_url).await {
Ok(client) => {
let token_info = client.get_token_info().await.map_err(|e| {
crate::error::QuantusError::NetworkError(format!(
"ChainHead token info failed: {e:?}"
))
})?;
let chain_name = client.get_chain_name().await.ok();
let genesis_hash = client.get_genesis_hash().await.ok();
Ok(ChainInfo { token: token_info, chain_name, genesis_hash })
},
Err(e) => {
log_verbose!("❌ ChainHead client creation failed: {:?}", e);
Err(crate::error::QuantusError::NetworkError(format!("ChainHead client failed: {e:?}")))
},
}
}
pub async fn get_system_info(quantus_client: &QuantusClient) -> crate::error::Result<()> {
log_verbose!("🔍 Querying system information...");
let chain_info = get_complete_chain_info(quantus_client.node_url()).await?;
let metadata = quantus_client.client().metadata();
let pallets: Vec<_> = metadata.pallets().collect();
log_print!("🏗️ Chain System Information:");
log_print!(
" 💰 Token: {} ({} decimals)",
chain_info.token.symbol.bright_yellow(),
chain_info.token.decimals.to_string().bright_cyan()
);
if let Some(ss58_format) = chain_info.token.ss58_format {
log_print!(" 🔢 SS58 Format: {}", ss58_format.to_string().bright_magenta());
}
if let Some(name) = &chain_info.chain_name {
log_print!(" 🔗 Chain: {}", name.bright_green());
}
if let Some(hash) = &chain_info.genesis_hash {
log_print!(" 🧬 Genesis: {}...", hash[..16].bright_cyan());
}
log_print!(" 📦 Pallets: {}", pallets.len().to_string());
log_print!(" 🔧 Runtime: Substrate-based");
log_verbose!("💡 Use 'quantus metadata' to explore all available pallets and calls");
log_verbose!("✅ System info retrieved successfully!");
Ok(())
}
pub async fn get_detailed_chain_params(
quantus_client: &crate::chain::client::QuantusClient,
show_raw_data: bool,
) -> crate::error::Result<()> {
log_print!("🔧 Runtime Information:");
let genesis_hash = quantus_client.get_genesis_hash().await?;
log_print!(" 🧬 Genesis hash: {}", genesis_hash.to_string().bright_cyan());
let (spec_version, transaction_version) = quantus_client.get_runtime_version().await?;
log_print!(" 📋 Spec version: {}", spec_version.to_string().bright_green());
log_print!(" 🔄 Transaction version: {}", transaction_version.to_string().bright_yellow());
use jsonrpsee::core::client::ClientT;
let current_block: serde_json::Value = quantus_client
.rpc_client()
.request::<serde_json::Value, [(); 0]>("chain_getHeader", [])
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!(
"Failed to fetch current block: {e:?}"
))
})?;
if let Some(block_number_str) = current_block["number"].as_str() {
if let Ok(block_number) = u64::from_str_radix(&block_number_str[2..], 16) {
log_print!(" 📦 Current block: {}", block_number.to_string().bright_blue());
let period = 64u64;
let phase = block_number % period;
log_print!(" ⏰ Era: period={}, phase={}", period, phase);
log_print!(
" 💡 Transaction era: Era::Mortal({}, {}) or Era::Immortal",
period,
phase
);
}
}
let runtime_info: serde_json::Value = quantus_client
.rpc_client()
.request::<serde_json::Value, [(); 0]>("state_getRuntimeVersion", [])
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Failed to fetch runtime info: {e:?}"))
})?;
if show_raw_data {
log_verbose!("📋 Full runtime info: {:?}", runtime_info);
}
log_print!("📋 Runtime Details:");
log_print!(
" 🏷️ Spec name: {}",
runtime_info["specName"].as_str().unwrap_or("unknown").bright_magenta()
);
log_print!(
" 🔧 Implementation name: {}",
runtime_info["implName"].as_str().unwrap_or("unknown").bright_cyan()
);
log_print!(
" 📦 Implementation version: {}",
runtime_info["implVersion"].as_u64().unwrap_or(0).to_string().bright_yellow()
);
log_print!(
" ✍️ Authoring version: {}",
runtime_info["authoringVersion"].as_u64().unwrap_or(0).to_string().bright_blue()
);
log_print!(
" 🗂️ State version: {}",
runtime_info["stateVersion"].as_u64().unwrap_or(0).to_string().bright_green()
);
log_print!(
" 💻 System version: {}",
runtime_info["systemVersion"].as_u64().unwrap_or(0).to_string().bright_red()
);
let chain_props: serde_json::Value = quantus_client
.rpc_client()
.request::<serde_json::Value, [(); 0]>("system_properties", [])
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!(
"Failed to fetch chain properties: {e:?}"
))
})?;
if show_raw_data {
log_verbose!("🔗 Chain properties: {:?}", chain_props);
}
if show_raw_data {
log_verbose!("📦 Current block: {:?}", current_block);
}
log_print!("📦 Block Details:");
if let Some(parent_hash) = current_block["parentHash"].as_str() {
log_print!(" 🔗 Parent hash: {}...", parent_hash[..16].bright_cyan());
}
if let Some(state_root) = current_block["stateRoot"].as_str() {
log_print!(" 🗂️ State root: {}...", state_root[..16].bright_magenta());
}
if let Some(extrinsics_root) = current_block["extrinsicsRoot"].as_str() {
log_print!(" 📄 Extrinsics root: {}...", extrinsics_root[..16].bright_yellow());
}
Ok(())
}
pub async fn get_metadata_stats(client: &OnlineClient<ChainConfig>) -> crate::error::Result<()> {
log_verbose!("🔍 Getting metadata statistics...");
let metadata = client.metadata();
let pallets: Vec<_> = metadata.pallets().collect();
log_verbose!("🔍 SubXT metadata: {} pallets available", pallets.len());
log_print!("📊 Metadata Statistics:");
log_print!(" 📦 Total pallets: {}", pallets.len());
log_print!(" 🔗 Metadata version: SubXT (type-safe)");
let mut total_calls = 0;
for pallet in &pallets {
if let Some(calls) = pallet.call_variants() {
total_calls += calls.len();
}
}
log_print!(" 🎯 Total calls: {}", total_calls);
log_print!(" ⚡ API: Type-safe SubXT");
Ok(())
}
pub async fn handle_system_command(node_url: &str) -> crate::error::Result<()> {
log_print!("🚀 System Information");
let quantus_client = QuantusClient::new(node_url).await?;
get_system_info(&quantus_client).await?;
Ok(())
}
pub async fn handle_system_extended_command(
node_url: &str,
show_runtime: bool,
show_metadata: bool,
show_rpc_methods: bool,
verbose: bool,
) -> crate::error::Result<()> {
log_print!("🚀 Extended System Information");
let quantus_client = QuantusClient::new(node_url).await?;
get_system_info(&quantus_client).await?;
if show_runtime {
log_print!("");
get_detailed_chain_params(&quantus_client, verbose).await?;
}
if show_metadata {
log_print!("");
get_metadata_stats(quantus_client.client()).await?;
}
if show_rpc_methods {
log_print!("");
list_rpc_methods(&quantus_client).await?;
}
Ok(())
}
async fn list_rpc_methods(quantus_client: &QuantusClient) -> crate::error::Result<()> {
use jsonrpsee::core::client::ClientT;
log_print!("🧭 JSON-RPC Methods exposed by node:");
let response: serde_json::Value = quantus_client
.rpc_client()
.request::<serde_json::Value, [(); 0]>("rpc_methods", [])
.await
.map_err(|e| {
crate::error::QuantusError::NetworkError(format!("Failed to fetch rpc_methods: {e:?}"))
})?;
if let Some(methods) = response.get("methods").and_then(|v| v.as_array()) {
for method in methods {
if let Some(name) = method.as_str() {
log_print!("\t{}", name);
}
}
if let Some(version) = response.get("version").and_then(|v| v.as_u64()) {
log_verbose!("RPC methods schema version: {}", version);
}
} else if let Some(array) = response.as_array() {
for method in array {
if let Some(name) = method.as_str() {
log_print!("\t{}", name);
}
}
} else {
log_print!(" (no methods returned or unexpected format)");
log_verbose!("rpc_methods raw response: {:?}", response);
}
Ok(())
}