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 multisend;
14pub mod multisig;
15pub mod preimage;
16pub mod recovery;
17pub mod reversible;
18pub mod runtime;
19pub mod scheduler;
20pub mod send;
21pub mod storage;
22pub mod system;
23pub mod tech_collective;
24pub mod transfers;
25pub mod treasury;
26pub mod wallet;
27pub mod wormhole;
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 Multisig(multisig::MultisigCommands),
86
87 #[command(subcommand)]
89 Scheduler(scheduler::SchedulerCommands),
90
91 #[command(subcommand)]
93 Storage(storage::StorageCommands),
94
95 #[command(subcommand)]
97 TechCollective(tech_collective::TechCollectiveCommands),
98
99 #[command(subcommand)]
101 Preimage(preimage::PreimageCommands),
102
103 #[command(subcommand)]
105 Treasury(treasury::TreasuryCommands),
106
107 #[command(subcommand)]
109 Transfers(transfers::TransfersCommands),
110
111 #[command(subcommand)]
113 Runtime(runtime::RuntimeCommands),
114
115 Call {
117 #[arg(long)]
119 pallet: String,
120
121 #[arg(short, long)]
123 call: String,
124
125 #[arg(short, long)]
128 args: Option<String>,
129
130 #[arg(short, long)]
132 from: String,
133
134 #[arg(short, long)]
136 password: Option<String>,
137
138 #[arg(long)]
140 password_file: Option<String>,
141
142 #[arg(long)]
144 tip: Option<String>,
145
146 #[arg(long)]
148 offline: bool,
149
150 #[arg(long)]
152 call_data_only: bool,
153 },
154
155 Balance {
157 #[arg(short, long)]
159 address: String,
160 },
161
162 #[command(subcommand)]
164 Developer(DeveloperCommands),
165
166 Events {
168 #[arg(long)]
170 block: Option<u32>,
171
172 #[arg(long)]
174 block_hash: Option<String>,
175
176 #[arg(long)]
178 latest: bool,
179
180 #[arg(long)]
182 finalized: bool,
183
184 #[arg(long)]
186 pallet: Option<String>,
187
188 #[arg(long)]
190 raw: bool,
191
192 #[arg(long)]
194 no_decode: bool,
195 },
196
197 System {
199 #[arg(long)]
201 runtime: bool,
202
203 #[arg(long)]
205 metadata: bool,
206
207 #[arg(long)]
209 rpc_methods: bool,
210 },
211
212 Metadata {
214 #[arg(long)]
216 no_docs: bool,
217
218 #[arg(long)]
220 stats_only: bool,
221
222 #[arg(long)]
224 pallet: Option<String>,
225 },
226
227 Version,
229
230 CompatibilityCheck,
232
233 #[command(subcommand)]
235 Block(block::BlockCommands),
236
237 #[command(subcommand)]
239 Wormhole(wormhole::WormholeCommands),
240
241 Multisend {
243 #[arg(short, long)]
245 from: String,
246
247 #[arg(long, conflicts_with = "addresses")]
249 addresses_file: Option<String>,
250
251 #[arg(long, value_delimiter = ',', conflicts_with = "addresses_file")]
253 addresses: Option<Vec<String>>,
254
255 #[arg(long)]
257 total: String,
258
259 #[arg(long)]
261 min: String,
262
263 #[arg(long)]
265 max: String,
266
267 #[arg(short, long)]
269 password: Option<String>,
270
271 #[arg(long)]
273 password_file: Option<String>,
274
275 #[arg(long)]
277 tip: Option<String>,
278
279 #[arg(long, short = 'y')]
281 yes: bool,
282 },
283}
284
285#[derive(Subcommand, Debug)]
287pub enum DeveloperCommands {
288 CreateTestWallets,
290}
291
292pub async fn execute_command(
294 command: Commands,
295 node_url: &str,
296 verbose: bool,
297 execution_mode: common::ExecutionMode,
298) -> crate::error::Result<()> {
299 match command {
300 Commands::Wallet(wallet_cmd) => wallet::handle_wallet_command(wallet_cmd, node_url).await,
301 Commands::Send { from, to, amount, password, password_file, tip, nonce } =>
302 send::handle_send_command(
303 from,
304 to,
305 &amount,
306 node_url,
307 password,
308 password_file,
309 tip,
310 nonce,
311 execution_mode,
312 )
313 .await,
314 Commands::Batch(batch_cmd) =>
315 batch::handle_batch_command(batch_cmd, node_url, execution_mode).await,
316 Commands::Reversible(reversible_cmd) =>
317 reversible::handle_reversible_command(reversible_cmd, node_url, execution_mode).await,
318 Commands::HighSecurity(hs_cmd) =>
319 high_security::handle_high_security_command(hs_cmd, node_url, execution_mode).await,
320 Commands::Recovery(recovery_cmd) =>
321 recovery::handle_recovery_command(recovery_cmd, node_url, execution_mode).await,
322 Commands::Multisig(multisig_cmd) =>
323 multisig::handle_multisig_command(multisig_cmd, node_url, execution_mode).await,
324 Commands::Scheduler(scheduler_cmd) =>
325 scheduler::handle_scheduler_command(scheduler_cmd, node_url, execution_mode).await,
326 Commands::Storage(storage_cmd) =>
327 storage::handle_storage_command(storage_cmd, node_url, execution_mode).await,
328 Commands::TechCollective(tech_collective_cmd) =>
329 tech_collective::handle_tech_collective_command(
330 tech_collective_cmd,
331 node_url,
332 execution_mode,
333 )
334 .await,
335 Commands::Preimage(preimage_cmd) =>
336 preimage::handle_preimage_command(preimage_cmd, node_url, execution_mode).await,
337 Commands::Treasury(treasury_cmd) =>
338 treasury::handle_treasury_command(treasury_cmd, node_url, execution_mode).await,
339 Commands::Transfers(transfers_cmd) =>
340 transfers::handle_transfers_command(transfers_cmd).await,
341 Commands::Runtime(runtime_cmd) =>
342 runtime::handle_runtime_command(runtime_cmd, node_url, execution_mode).await,
343 Commands::Call {
344 pallet,
345 call,
346 args,
347 from,
348 password,
349 password_file,
350 tip,
351 offline,
352 call_data_only,
353 } =>
354 handle_generic_call_command(
355 pallet,
356 call,
357 args,
358 from,
359 password,
360 password_file,
361 tip,
362 offline,
363 call_data_only,
364 node_url,
365 execution_mode,
366 )
367 .await,
368 Commands::Balance { address } => {
369 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
370
371 let resolved_address = common::resolve_address(&address)?;
373
374 let account_data = send::get_account_data(&quantus_client, &resolved_address).await?;
375 let (symbol, decimals) = send::get_chain_properties(&quantus_client).await?;
376
377 let free_fmt = send::format_balance(account_data.free, decimals);
378 let reserved_fmt = send::format_balance(account_data.reserved, decimals);
379 let frozen_fmt = send::format_balance(account_data.frozen, decimals);
380
381 log_print!("๐ฐ {} {}", "Balance".bright_green().bold(), resolved_address.bright_cyan());
382 log_print!(" Free: {} {}", free_fmt.bright_green(), symbol);
383 log_print!(" Reserved: {} {}", reserved_fmt.bright_yellow(), symbol);
384 log_print!(" Frozen: {} {}", frozen_fmt.bright_red(), symbol);
385 Ok(())
386 },
387 Commands::Developer(dev_cmd) => handle_developer_command(dev_cmd).await,
388 Commands::Events { block, block_hash, latest: _, finalized, pallet, raw, no_decode } =>
389 events::handle_events_command(
390 block, block_hash, finalized, pallet, raw, !no_decode, node_url,
391 )
392 .await,
393 Commands::System { runtime, metadata, rpc_methods } => {
394 if runtime || metadata || rpc_methods {
395 system::handle_system_extended_command(
396 node_url,
397 runtime,
398 metadata,
399 rpc_methods,
400 verbose,
401 )
402 .await
403 } else {
404 system::handle_system_command(node_url).await
405 }
406 },
407 Commands::Metadata { no_docs, stats_only, pallet } =>
408 metadata::handle_metadata_command(node_url, no_docs, stats_only, pallet).await,
409 Commands::Version => {
410 log_print!("CLI Version: Quantus CLI v{}", env!("CARGO_PKG_VERSION"));
411 Ok(())
412 },
413 Commands::CompatibilityCheck => handle_compatibility_check(node_url).await,
414 Commands::Block(block_cmd) => block::handle_block_command(block_cmd, node_url).await,
415 Commands::Wormhole(wormhole_cmd) =>
416 wormhole::handle_wormhole_command(wormhole_cmd, node_url).await,
417 Commands::Multisend {
418 from,
419 addresses_file,
420 addresses,
421 total,
422 min,
423 max,
424 password,
425 password_file,
426 tip,
427 yes,
428 } =>
429 multisend::handle_multisend_command(
430 from,
431 node_url,
432 addresses_file,
433 addresses,
434 total,
435 min,
436 max,
437 password,
438 password_file,
439 tip,
440 yes,
441 execution_mode,
442 )
443 .await,
444 }
445}
446
447#[allow(clippy::too_many_arguments)]
449async fn handle_generic_call_command(
450 pallet: String,
451 call: String,
452 args: Option<String>,
453 from: String,
454 password: Option<String>,
455 password_file: Option<String>,
456 tip: Option<String>,
457 offline: bool,
458 call_data_only: bool,
459 node_url: &str,
460 execution_mode: common::ExecutionMode,
461) -> crate::error::Result<()> {
462 if offline {
464 log_error!("โ Offline mode is not yet implemented");
465 log_print!("๐ก Currently only live submission is supported");
466 return Ok(());
467 }
468
469 if call_data_only {
470 log_error!("โ Call-data-only mode is not yet implemented");
471 log_print!("๐ก Currently only live submission is supported");
472 return Ok(());
473 }
474
475 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
476
477 let args_vec = if let Some(args_str) = args {
478 serde_json::from_str(&args_str).map_err(|e| {
479 crate::error::QuantusError::Generic(format!("Invalid JSON for arguments: {e}"))
480 })?
481 } else {
482 vec![]
483 };
484
485 generic_call::handle_generic_call(
486 &pallet,
487 &call,
488 args_vec,
489 &keypair,
490 tip,
491 node_url,
492 execution_mode,
493 )
494 .await
495}
496
497pub async fn handle_developer_command(command: DeveloperCommands) -> crate::error::Result<()> {
499 match command {
500 DeveloperCommands::CreateTestWallets => {
501 use crate::wallet::WalletManager;
502
503 log_print!(
504 "๐งช {} Creating standard test wallets...",
505 "DEVELOPER".bright_magenta().bold()
506 );
507 log_print!("");
508
509 let wallet_manager = WalletManager::new()?;
510
511 let test_wallets = vec![
513 ("crystal_alice", "Alice's test wallet for development"),
514 ("crystal_bob", "Bob's test wallet for development"),
515 ("crystal_charlie", "Charlie's test wallet for development"),
516 ];
517
518 let mut created_count = 0;
519
520 for (name, description) in test_wallets {
521 log_verbose!("Creating wallet: {}", name.bright_green());
522
523 match wallet_manager.create_developer_wallet(name).await {
525 Ok(wallet_info) => {
526 log_success!("โ
Created {}", name.bright_green());
527 log_success!(" Address: {}", wallet_info.address.bright_cyan());
528 log_success!(" Description: {}", description.dimmed());
529 created_count += 1;
530 },
531 Err(e) => {
532 log_error!("โ Failed to create {}: {}", name.bright_red(), e);
533 },
534 }
535 }
536
537 log_print!("");
538 log_success!("๐ Test wallet creation complete!");
539 log_success!(" Created: {} wallets", created_count.to_string().bright_green());
540 log_print!("");
541 log_print!("๐ก {} You can now use these wallets:", "TIP".bright_blue().bold());
542 log_print!(" quantus send --from crystal_alice --to <address> --amount 1000");
543 log_print!(" quantus send --from crystal_bob --to <address> --amount 1000");
544 log_print!(" quantus send --from crystal_charlie --to <address> --amount 1000");
545 log_print!("");
546
547 Ok(())
548 },
549 }
550}
551
552async fn handle_compatibility_check(node_url: &str) -> crate::error::Result<()> {
554 log_print!("๐ Compatibility Check");
555 log_print!("๐ Connecting to: {}", node_url.bright_cyan());
556 log_print!("");
557
558 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
560
561 let runtime_version = runtime::get_runtime_version(quantus_client.client()).await?;
563
564 let chain_info = system::get_complete_chain_info(node_url).await?;
566
567 log_print!("๐ Version Information:");
568 log_print!(" โข CLI Version: {}", env!("CARGO_PKG_VERSION").bright_green());
569 log_print!(
570 " โข Runtime Spec Version: {}",
571 runtime_version.spec_version.to_string().bright_yellow()
572 );
573 log_print!(
574 " โข Runtime Impl Version: {}",
575 runtime_version.impl_version.to_string().bright_blue()
576 );
577 log_print!(
578 " โข Transaction Version: {}",
579 runtime_version.transaction_version.to_string().bright_magenta()
580 );
581
582 if let Some(name) = &chain_info.chain_name {
583 log_print!(" โข Chain Name: {}", name.bright_cyan());
584 }
585
586 log_print!("");
587
588 let is_compatible = crate::config::is_runtime_compatible(runtime_version.spec_version);
590
591 log_print!("๐ Compatibility Analysis:");
592 log_print!(" โข Supported Runtime Versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
593 log_print!(" โข Current Runtime Version: {}", runtime_version.spec_version);
594
595 if is_compatible {
596 log_success!("โ
COMPATIBLE - This CLI version supports the connected node");
597 log_print!(" โข All features should work correctly");
598 log_print!(" โข You can safely use all CLI commands");
599 } else {
600 log_error!("โ INCOMPATIBLE - This CLI version may not work with the connected node");
601 log_print!(" โข Some features may not work correctly");
602 log_print!(" โข Consider updating the CLI or connecting to a compatible node");
603 log_print!(" โข Supported versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
604 }
605
606 log_print!("");
607 log_print!("๐ก Tip: Use 'quantus version' for quick version check");
608 log_print!("๐ก Tip: Use 'quantus system --runtime' for detailed system info");
609
610 Ok(())
611}