1use crate::{log_error, log_print, log_success, log_verbose};
2use clap::Subcommand;
3use colored::Colorize;
4
5pub mod address_format;
6pub mod batch;
7pub mod block;
8pub mod common;
9pub mod events;
10pub mod generic_call;
11pub mod high_security;
12pub mod metadata;
13pub mod preimage;
14pub mod progress_spinner;
15pub mod recovery;
16pub mod referenda;
17pub mod referenda_decode;
18pub mod reversible;
19pub mod runtime;
20pub mod scheduler;
21pub mod send;
22pub mod storage;
23pub mod system;
24pub mod tech_collective;
25pub mod tech_referenda;
26pub mod treasury;
27pub mod wallet;
28
29#[derive(Subcommand, Debug)]
31pub enum Commands {
32 #[command(subcommand)]
34 Wallet(wallet::WalletCommands),
35
36 Send {
38 #[arg(short, long)]
40 to: String,
41
42 #[arg(short, long)]
44 amount: String,
45
46 #[arg(short, long)]
48 from: String,
49
50 #[arg(short, long)]
52 password: Option<String>,
53
54 #[arg(long)]
56 password_file: Option<String>,
57
58 #[arg(long)]
60 tip: Option<String>,
61
62 #[arg(long)]
64 nonce: Option<u32>,
65 },
66
67 #[command(subcommand)]
69 Batch(batch::BatchCommands),
70
71 #[command(subcommand)]
73 Reversible(reversible::ReversibleCommands),
74
75 #[command(subcommand)]
77 HighSecurity(high_security::HighSecurityCommands),
78
79 #[command(subcommand)]
81 Recovery(recovery::RecoveryCommands),
82
83 #[command(subcommand)]
85 Scheduler(scheduler::SchedulerCommands),
86
87 #[command(subcommand)]
89 Storage(storage::StorageCommands),
90
91 #[command(subcommand)]
93 TechCollective(tech_collective::TechCollectiveCommands),
94
95 #[command(subcommand)]
97 Preimage(preimage::PreimageCommands),
98 #[command(subcommand)]
99 TechReferenda(tech_referenda::TechReferendaCommands),
100
101 #[command(subcommand)]
103 Referenda(referenda::ReferendaCommands),
104
105 #[command(subcommand)]
107 Treasury(treasury::TreasuryCommands),
108
109 #[command(subcommand)]
111 Runtime(runtime::RuntimeCommands),
112
113 Call {
115 #[arg(long)]
117 pallet: String,
118
119 #[arg(short, long)]
121 call: String,
122
123 #[arg(short, long)]
126 args: Option<String>,
127
128 #[arg(short, long)]
130 from: String,
131
132 #[arg(short, long)]
134 password: Option<String>,
135
136 #[arg(long)]
138 password_file: Option<String>,
139
140 #[arg(long)]
142 tip: Option<String>,
143
144 #[arg(long)]
146 offline: bool,
147
148 #[arg(long)]
150 call_data_only: bool,
151 },
152
153 Balance {
155 #[arg(short, long)]
157 address: String,
158 },
159
160 #[command(subcommand)]
162 Developer(DeveloperCommands),
163
164 Events {
166 #[arg(long)]
168 block: Option<u32>,
169
170 #[arg(long)]
172 block_hash: Option<String>,
173
174 #[arg(long)]
176 latest: bool,
177
178 #[arg(long)]
180 finalized: bool,
181
182 #[arg(long)]
184 pallet: Option<String>,
185
186 #[arg(long)]
188 raw: bool,
189
190 #[arg(long)]
192 no_decode: bool,
193 },
194
195 System {
197 #[arg(long)]
199 runtime: bool,
200
201 #[arg(long)]
203 metadata: bool,
204
205 #[arg(long)]
207 rpc_methods: bool,
208 },
209
210 Metadata {
212 #[arg(long)]
214 no_docs: bool,
215
216 #[arg(long)]
218 stats_only: bool,
219
220 #[arg(long)]
222 pallet: Option<String>,
223 },
224
225 Version,
227
228 CompatibilityCheck,
230
231 #[command(subcommand)]
233 Block(block::BlockCommands),
234}
235
236#[derive(Subcommand, Debug)]
238pub enum DeveloperCommands {
239 CreateTestWallets,
241}
242
243pub async fn execute_command(
245 command: Commands,
246 node_url: &str,
247 verbose: bool,
248) -> crate::error::Result<()> {
249 match command {
250 Commands::Wallet(wallet_cmd) => wallet::handle_wallet_command(wallet_cmd, node_url).await,
251 Commands::Send { from, to, amount, password, password_file, tip, nonce } =>
252 send::handle_send_command(
253 from,
254 to,
255 &amount,
256 node_url,
257 password,
258 password_file,
259 tip,
260 nonce,
261 )
262 .await,
263 Commands::Batch(batch_cmd) => batch::handle_batch_command(batch_cmd, node_url).await,
264 Commands::Reversible(reversible_cmd) =>
265 reversible::handle_reversible_command(reversible_cmd, node_url).await,
266 Commands::HighSecurity(hs_cmd) =>
267 high_security::handle_high_security_command(hs_cmd, node_url).await,
268 Commands::Recovery(recovery_cmd) =>
269 recovery::handle_recovery_command(recovery_cmd, node_url).await,
270 Commands::Scheduler(scheduler_cmd) =>
271 scheduler::handle_scheduler_command(scheduler_cmd, node_url).await,
272 Commands::Storage(storage_cmd) =>
273 storage::handle_storage_command(storage_cmd, node_url).await,
274 Commands::TechCollective(tech_collective_cmd) =>
275 tech_collective::handle_tech_collective_command(tech_collective_cmd, node_url).await,
276 Commands::Preimage(preimage_cmd) =>
277 preimage::handle_preimage_command(preimage_cmd, node_url).await,
278 Commands::TechReferenda(tech_referenda_cmd) =>
279 tech_referenda::handle_tech_referenda_command(tech_referenda_cmd, node_url).await,
280 Commands::Referenda(referenda_cmd) =>
281 referenda::handle_referenda_command(referenda_cmd, node_url).await,
282 Commands::Treasury(treasury_cmd) =>
283 treasury::handle_treasury_command(treasury_cmd, node_url).await,
284 Commands::Runtime(runtime_cmd) =>
285 runtime::handle_runtime_command(runtime_cmd, node_url).await,
286 Commands::Call {
287 pallet,
288 call,
289 args,
290 from,
291 password,
292 password_file,
293 tip,
294 offline,
295 call_data_only,
296 } =>
297 handle_generic_call_command(
298 pallet,
299 call,
300 args,
301 from,
302 password,
303 password_file,
304 tip,
305 offline,
306 call_data_only,
307 node_url,
308 )
309 .await,
310 Commands::Balance { address } => {
311 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
312
313 let resolved_address = common::resolve_address(&address)?;
315
316 let balance = send::get_balance(&quantus_client, &resolved_address).await?;
317 let formatted_balance =
318 send::format_balance_with_symbol(&quantus_client, balance).await?;
319 log_print!("๐ฐ Balance: {}", formatted_balance);
320 Ok(())
321 },
322 Commands::Developer(dev_cmd) => match dev_cmd {
323 DeveloperCommands::CreateTestWallets => {
324 let _ = crate::cli::handle_developer_command(DeveloperCommands::CreateTestWallets)
325 .await;
326 Ok(())
327 },
328 },
329 Commands::Events { block, block_hash, latest: _, finalized, pallet, raw, no_decode } =>
330 events::handle_events_command(
331 block, block_hash, finalized, pallet, raw, !no_decode, node_url,
332 )
333 .await,
334 Commands::System { runtime, metadata, rpc_methods } => {
335 if runtime || metadata || rpc_methods {
336 system::handle_system_extended_command(
337 node_url,
338 runtime,
339 metadata,
340 rpc_methods,
341 verbose,
342 )
343 .await
344 } else {
345 system::handle_system_command(node_url).await
346 }
347 },
348 Commands::Metadata { no_docs, stats_only, pallet } =>
349 metadata::handle_metadata_command(node_url, no_docs, stats_only, pallet).await,
350 Commands::Version => {
351 log_print!("CLI Version: Quantus CLI v{}", env!("CARGO_PKG_VERSION"));
352 Ok(())
353 },
354 Commands::CompatibilityCheck => handle_compatibility_check(node_url).await,
355 Commands::Block(block_cmd) => block::handle_block_command(block_cmd, node_url).await,
356 }
357}
358
359async fn handle_generic_call_command(
361 pallet: String,
362 call: String,
363 args: Option<String>,
364 from: String,
365 password: Option<String>,
366 password_file: Option<String>,
367 tip: Option<String>,
368 offline: bool,
369 call_data_only: bool,
370 node_url: &str,
371) -> crate::error::Result<()> {
372 if offline {
374 log_error!("โ Offline mode is not yet implemented");
375 log_print!("๐ก Currently only live submission is supported");
376 return Ok(());
377 }
378
379 if call_data_only {
380 log_error!("โ Call-data-only mode is not yet implemented");
381 log_print!("๐ก Currently only live submission is supported");
382 return Ok(());
383 }
384
385 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
386
387 let args_vec = if let Some(args_str) = args {
388 serde_json::from_str(&args_str).map_err(|e| {
389 crate::error::QuantusError::Generic(format!("Invalid JSON for arguments: {e}"))
390 })?
391 } else {
392 vec![]
393 };
394
395 generic_call::handle_generic_call(&pallet, &call, args_vec, &keypair, tip, node_url).await
396}
397
398pub async fn handle_developer_command(command: DeveloperCommands) -> crate::error::Result<()> {
400 match command {
401 DeveloperCommands::CreateTestWallets => {
402 use crate::wallet::WalletManager;
403
404 log_print!(
405 "๐งช {} Creating standard test wallets...",
406 "DEVELOPER".bright_magenta().bold()
407 );
408 log_print!("");
409
410 let wallet_manager = WalletManager::new()?;
411
412 let test_wallets = vec![
414 ("crystal_alice", "Alice's test wallet for development"),
415 ("crystal_bob", "Bob's test wallet for development"),
416 ("crystal_charlie", "Charlie's test wallet for development"),
417 ];
418
419 let mut created_count = 0;
420
421 for (name, description) in test_wallets {
422 log_verbose!("Creating wallet: {}", name.bright_green());
423
424 match wallet_manager.create_developer_wallet(name).await {
426 Ok(wallet_info) => {
427 log_success!("โ
Created {}", name.bright_green());
428 log_success!(" Address: {}", wallet_info.address.bright_cyan());
429 log_success!(" Description: {}", description.dimmed());
430 created_count += 1;
431 },
432 Err(e) => {
433 log_error!("โ Failed to create {}: {}", name.bright_red(), e);
434 },
435 }
436 }
437
438 log_print!("");
439 log_success!("๐ Test wallet creation complete!");
440 log_success!(" Created: {} wallets", created_count.to_string().bright_green());
441 log_print!("");
442 log_print!("๐ก {} You can now use these wallets:", "TIP".bright_blue().bold());
443 log_print!(" quantus send --from crystal_alice --to <address> --amount 1000");
444 log_print!(" quantus send --from crystal_bob --to <address> --amount 1000");
445 log_print!(" quantus send --from crystal_charlie --to <address> --amount 1000");
446 log_print!("");
447
448 Ok(())
449 },
450 }
451}
452
453async fn handle_compatibility_check(node_url: &str) -> crate::error::Result<()> {
455 log_print!("๐ Compatibility Check");
456 log_print!("๐ Connecting to: {}", node_url.bright_cyan());
457 log_print!("");
458
459 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
461
462 let runtime_version = runtime::get_runtime_version(quantus_client.client()).await?;
464
465 let chain_info = system::get_complete_chain_info(node_url).await?;
467
468 log_print!("๐ Version Information:");
469 log_print!(" โข CLI Version: {}", env!("CARGO_PKG_VERSION").bright_green());
470 log_print!(
471 " โข Runtime Spec Version: {}",
472 runtime_version.spec_version.to_string().bright_yellow()
473 );
474 log_print!(
475 " โข Runtime Impl Version: {}",
476 runtime_version.impl_version.to_string().bright_blue()
477 );
478 log_print!(
479 " โข Transaction Version: {}",
480 runtime_version.transaction_version.to_string().bright_magenta()
481 );
482
483 if let Some(name) = &chain_info.chain_name {
484 log_print!(" โข Chain Name: {}", name.bright_cyan());
485 }
486
487 log_print!("");
488
489 let is_compatible = crate::config::is_runtime_compatible(runtime_version.spec_version);
491
492 log_print!("๐ Compatibility Analysis:");
493 log_print!(" โข Supported Runtime Versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
494 log_print!(" โข Current Runtime Version: {}", runtime_version.spec_version);
495
496 if is_compatible {
497 log_success!("โ
COMPATIBLE - This CLI version supports the connected node");
498 log_print!(" โข All features should work correctly");
499 log_print!(" โข You can safely use all CLI commands");
500 } else {
501 log_error!("โ INCOMPATIBLE - This CLI version may not work with the connected node");
502 log_print!(" โข Some features may not work correctly");
503 log_print!(" โข Consider updating the CLI or connecting to a compatible node");
504 log_print!(" โข Supported versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
505 }
506
507 log_print!("");
508 log_print!("๐ก Tip: Use 'quantus version' for quick version check");
509 log_print!("๐ก Tip: Use 'quantus system --runtime' for detailed system info");
510
511 Ok(())
512}