use crate::{log_error, log_print, log_success, log_verbose};
use clap::Subcommand;
use colored::Colorize;
pub mod address_format;
pub mod batch;
pub mod block;
pub mod common;
pub mod events;
pub mod generic_call;
pub mod high_security;
pub mod metadata;
pub mod multisend;
pub mod multisig;
pub mod preimage;
pub mod recovery;
pub mod referenda;
pub mod referenda_decode;
pub mod reversible;
pub mod runtime;
pub mod scheduler;
pub mod send;
pub mod storage;
pub mod system;
pub mod tech_collective;
pub mod tech_referenda;
pub mod transfers;
pub mod treasury;
pub mod wallet;
pub mod wormhole;
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(subcommand)]
Wallet(wallet::WalletCommands),
Send {
#[arg(short, long)]
to: String,
#[arg(short, long)]
amount: String,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
#[arg(long)]
tip: Option<String>,
#[arg(long)]
nonce: Option<u32>,
},
#[command(subcommand)]
Batch(batch::BatchCommands),
#[command(subcommand)]
Reversible(reversible::ReversibleCommands),
#[command(subcommand)]
HighSecurity(high_security::HighSecurityCommands),
#[command(subcommand)]
Recovery(recovery::RecoveryCommands),
#[command(subcommand)]
Multisig(multisig::MultisigCommands),
#[command(subcommand)]
Scheduler(scheduler::SchedulerCommands),
#[command(subcommand)]
Storage(storage::StorageCommands),
#[command(subcommand)]
TechCollective(tech_collective::TechCollectiveCommands),
#[command(subcommand)]
Preimage(preimage::PreimageCommands),
#[command(subcommand)]
TechReferenda(tech_referenda::TechReferendaCommands),
#[command(subcommand)]
Referenda(referenda::ReferendaCommands),
#[command(subcommand)]
Treasury(treasury::TreasuryCommands),
#[command(subcommand)]
Transfers(transfers::TransfersCommands),
#[command(subcommand)]
Runtime(runtime::RuntimeCommands),
Call {
#[arg(long)]
pallet: String,
#[arg(short, long)]
call: String,
#[arg(short, long)]
args: Option<String>,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
#[arg(long)]
tip: Option<String>,
#[arg(long)]
offline: bool,
#[arg(long)]
call_data_only: bool,
},
Balance {
#[arg(short, long)]
address: String,
},
#[command(subcommand)]
Developer(DeveloperCommands),
Events {
#[arg(long)]
block: Option<u32>,
#[arg(long)]
block_hash: Option<String>,
#[arg(long)]
latest: bool,
#[arg(long)]
finalized: bool,
#[arg(long)]
pallet: Option<String>,
#[arg(long)]
raw: bool,
#[arg(long)]
no_decode: bool,
},
System {
#[arg(long)]
runtime: bool,
#[arg(long)]
metadata: bool,
#[arg(long)]
rpc_methods: bool,
},
Metadata {
#[arg(long)]
no_docs: bool,
#[arg(long)]
stats_only: bool,
#[arg(long)]
pallet: Option<String>,
},
Version,
CompatibilityCheck,
#[command(subcommand)]
Block(block::BlockCommands),
#[command(subcommand)]
Wormhole(wormhole::WormholeCommands),
Multisend {
#[arg(short, long)]
from: String,
#[arg(long, conflicts_with = "addresses")]
addresses_file: Option<String>,
#[arg(long, value_delimiter = ',', conflicts_with = "addresses_file")]
addresses: Option<Vec<String>>,
#[arg(long)]
total: String,
#[arg(long)]
min: String,
#[arg(long)]
max: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
#[arg(long)]
tip: Option<String>,
#[arg(long, short = 'y')]
yes: bool,
},
}
#[derive(Subcommand, Debug)]
pub enum DeveloperCommands {
CreateTestWallets,
}
pub async fn execute_command(
command: Commands,
node_url: &str,
verbose: bool,
execution_mode: common::ExecutionMode,
) -> crate::error::Result<()> {
match command {
Commands::Wallet(wallet_cmd) => wallet::handle_wallet_command(wallet_cmd, node_url).await,
Commands::Send { from, to, amount, password, password_file, tip, nonce } =>
send::handle_send_command(
from,
to,
&amount,
node_url,
password,
password_file,
tip,
nonce,
execution_mode,
)
.await,
Commands::Batch(batch_cmd) =>
batch::handle_batch_command(batch_cmd, node_url, execution_mode).await,
Commands::Reversible(reversible_cmd) =>
reversible::handle_reversible_command(reversible_cmd, node_url, execution_mode).await,
Commands::HighSecurity(hs_cmd) =>
high_security::handle_high_security_command(hs_cmd, node_url, execution_mode).await,
Commands::Recovery(recovery_cmd) =>
recovery::handle_recovery_command(recovery_cmd, node_url, execution_mode).await,
Commands::Multisig(multisig_cmd) =>
multisig::handle_multisig_command(multisig_cmd, node_url, execution_mode).await,
Commands::Scheduler(scheduler_cmd) =>
scheduler::handle_scheduler_command(scheduler_cmd, node_url, execution_mode).await,
Commands::Storage(storage_cmd) =>
storage::handle_storage_command(storage_cmd, node_url, execution_mode).await,
Commands::TechCollective(tech_collective_cmd) =>
tech_collective::handle_tech_collective_command(
tech_collective_cmd,
node_url,
execution_mode,
)
.await,
Commands::Preimage(preimage_cmd) =>
preimage::handle_preimage_command(preimage_cmd, node_url, execution_mode).await,
Commands::TechReferenda(tech_referenda_cmd) =>
tech_referenda::handle_tech_referenda_command(
tech_referenda_cmd,
node_url,
execution_mode,
)
.await,
Commands::Referenda(referenda_cmd) =>
referenda::handle_referenda_command(referenda_cmd, node_url, execution_mode).await,
Commands::Treasury(treasury_cmd) =>
treasury::handle_treasury_command(treasury_cmd, node_url, execution_mode).await,
Commands::Transfers(transfers_cmd) =>
transfers::handle_transfers_command(transfers_cmd).await,
Commands::Runtime(runtime_cmd) =>
runtime::handle_runtime_command(runtime_cmd, node_url, execution_mode).await,
Commands::Call {
pallet,
call,
args,
from,
password,
password_file,
tip,
offline,
call_data_only,
} =>
handle_generic_call_command(
pallet,
call,
args,
from,
password,
password_file,
tip,
offline,
call_data_only,
node_url,
execution_mode,
)
.await,
Commands::Balance { address } => {
let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
let resolved_address = common::resolve_address(&address)?;
let account_data = send::get_account_data(&quantus_client, &resolved_address).await?;
let (symbol, decimals) = send::get_chain_properties(&quantus_client).await?;
let free_fmt = send::format_balance(account_data.free, decimals);
let reserved_fmt = send::format_balance(account_data.reserved, decimals);
let frozen_fmt = send::format_balance(account_data.frozen, decimals);
log_print!("๐ฐ {} {}", "Balance".bright_green().bold(), resolved_address.bright_cyan());
log_print!(" Free: {} {}", free_fmt.bright_green(), symbol);
log_print!(" Reserved: {} {}", reserved_fmt.bright_yellow(), symbol);
log_print!(" Frozen: {} {}", frozen_fmt.bright_red(), symbol);
Ok(())
},
Commands::Developer(dev_cmd) => handle_developer_command(dev_cmd).await,
Commands::Events { block, block_hash, latest: _, finalized, pallet, raw, no_decode } =>
events::handle_events_command(
block, block_hash, finalized, pallet, raw, !no_decode, node_url,
)
.await,
Commands::System { runtime, metadata, rpc_methods } => {
if runtime || metadata || rpc_methods {
system::handle_system_extended_command(
node_url,
runtime,
metadata,
rpc_methods,
verbose,
)
.await
} else {
system::handle_system_command(node_url).await
}
},
Commands::Metadata { no_docs, stats_only, pallet } =>
metadata::handle_metadata_command(node_url, no_docs, stats_only, pallet).await,
Commands::Version => {
log_print!("CLI Version: Quantus CLI v{}", env!("CARGO_PKG_VERSION"));
Ok(())
},
Commands::CompatibilityCheck => handle_compatibility_check(node_url).await,
Commands::Block(block_cmd) => block::handle_block_command(block_cmd, node_url).await,
Commands::Wormhole(wormhole_cmd) =>
wormhole::handle_wormhole_command(wormhole_cmd, node_url).await,
Commands::Multisend {
from,
addresses_file,
addresses,
total,
min,
max,
password,
password_file,
tip,
yes,
} =>
multisend::handle_multisend_command(
from,
node_url,
addresses_file,
addresses,
total,
min,
max,
password,
password_file,
tip,
yes,
execution_mode,
)
.await,
}
}
#[allow(clippy::too_many_arguments)]
async fn handle_generic_call_command(
pallet: String,
call: String,
args: Option<String>,
from: String,
password: Option<String>,
password_file: Option<String>,
tip: Option<String>,
offline: bool,
call_data_only: bool,
node_url: &str,
execution_mode: common::ExecutionMode,
) -> crate::error::Result<()> {
if offline {
log_error!("โ Offline mode is not yet implemented");
log_print!("๐ก Currently only live submission is supported");
return Ok(());
}
if call_data_only {
log_error!("โ Call-data-only mode is not yet implemented");
log_print!("๐ก Currently only live submission is supported");
return Ok(());
}
let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
let args_vec = if let Some(args_str) = args {
serde_json::from_str(&args_str).map_err(|e| {
crate::error::QuantusError::Generic(format!("Invalid JSON for arguments: {e}"))
})?
} else {
vec![]
};
generic_call::handle_generic_call(
&pallet,
&call,
args_vec,
&keypair,
tip,
node_url,
execution_mode,
)
.await
}
pub async fn handle_developer_command(command: DeveloperCommands) -> crate::error::Result<()> {
match command {
DeveloperCommands::CreateTestWallets => {
use crate::wallet::WalletManager;
log_print!(
"๐งช {} Creating standard test wallets...",
"DEVELOPER".bright_magenta().bold()
);
log_print!("");
let wallet_manager = WalletManager::new()?;
let test_wallets = vec![
("crystal_alice", "Alice's test wallet for development"),
("crystal_bob", "Bob's test wallet for development"),
("crystal_charlie", "Charlie's test wallet for development"),
];
let mut created_count = 0;
for (name, description) in test_wallets {
log_verbose!("Creating wallet: {}", name.bright_green());
match wallet_manager.create_developer_wallet(name).await {
Ok(wallet_info) => {
log_success!("โ
Created {}", name.bright_green());
log_success!(" Address: {}", wallet_info.address.bright_cyan());
log_success!(" Description: {}", description.dimmed());
created_count += 1;
},
Err(e) => {
log_error!("โ Failed to create {}: {}", name.bright_red(), e);
},
}
}
log_print!("");
log_success!("๐ Test wallet creation complete!");
log_success!(" Created: {} wallets", created_count.to_string().bright_green());
log_print!("");
log_print!("๐ก {} You can now use these wallets:", "TIP".bright_blue().bold());
log_print!(" quantus send --from crystal_alice --to <address> --amount 1000");
log_print!(" quantus send --from crystal_bob --to <address> --amount 1000");
log_print!(" quantus send --from crystal_charlie --to <address> --amount 1000");
log_print!("");
Ok(())
},
}
}
async fn handle_compatibility_check(node_url: &str) -> crate::error::Result<()> {
log_print!("๐ Compatibility Check");
log_print!("๐ Connecting to: {}", node_url.bright_cyan());
log_print!("");
let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
let runtime_version = runtime::get_runtime_version(quantus_client.client()).await?;
let chain_info = system::get_complete_chain_info(node_url).await?;
log_print!("๐ Version Information:");
log_print!(" โข CLI Version: {}", env!("CARGO_PKG_VERSION").bright_green());
log_print!(
" โข Runtime Spec Version: {}",
runtime_version.spec_version.to_string().bright_yellow()
);
log_print!(
" โข Runtime Impl Version: {}",
runtime_version.impl_version.to_string().bright_blue()
);
log_print!(
" โข Transaction Version: {}",
runtime_version.transaction_version.to_string().bright_magenta()
);
if let Some(name) = &chain_info.chain_name {
log_print!(" โข Chain Name: {}", name.bright_cyan());
}
log_print!("");
let is_compatible = crate::config::is_runtime_compatible(runtime_version.spec_version);
log_print!("๐ Compatibility Analysis:");
log_print!(" โข Supported Runtime Versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
log_print!(" โข Current Runtime Version: {}", runtime_version.spec_version);
if is_compatible {
log_success!("โ
COMPATIBLE - This CLI version supports the connected node");
log_print!(" โข All features should work correctly");
log_print!(" โข You can safely use all CLI commands");
} else {
log_error!("โ INCOMPATIBLE - This CLI version may not work with the connected node");
log_print!(" โข Some features may not work correctly");
log_print!(" โข Consider updating the CLI or connecting to a compatible node");
log_print!(" โข Supported versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
}
log_print!("");
log_print!("๐ก Tip: Use 'quantus version' for quick version check");
log_print!("๐ก Tip: Use 'quantus system --runtime' for detailed system info");
Ok(())
}