quantus_cli/cli/
mod.rs

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 progress_spinner;
14pub mod recovery;
15pub mod reversible;
16pub mod runtime;
17pub mod scheduler;
18pub mod send;
19pub mod storage;
20pub mod system;
21pub mod tech_collective;
22pub mod wallet;
23
24/// Main CLI commands
25#[derive(Subcommand, Debug)]
26pub enum Commands {
27	/// Wallet management commands
28	#[command(subcommand)]
29	Wallet(wallet::WalletCommands),
30
31	/// Send tokens to another account
32	Send {
33		/// The recipient's account address
34		#[arg(short, long)]
35		to: String,
36
37		/// Amount to send (e.g., "10", "10.5", "0.0001")
38		#[arg(short, long)]
39		amount: String,
40
41		/// Wallet name to send from
42		#[arg(short, long)]
43		from: String,
44
45		/// Password for the wallet (or use environment variables)
46		#[arg(short, long)]
47		password: Option<String>,
48
49		/// Read password from file (for scripting)
50		#[arg(long)]
51		password_file: Option<String>,
52
53		/// Optional tip amount to prioritize the transaction (e.g., "1", "0.5")
54		#[arg(long)]
55		tip: Option<String>,
56
57		/// Manual nonce override (use with caution - must be exact next nonce for account)
58		#[arg(long)]
59		nonce: Option<u32>,
60	},
61
62	/// Batch transfer commands and configuration
63	#[command(subcommand)]
64	Batch(batch::BatchCommands),
65
66	/// Reversible transfer commands
67	#[command(subcommand)]
68	Reversible(reversible::ReversibleCommands),
69
70	/// High-Security commands (reversible account settings)
71	#[command(subcommand)]
72	HighSecurity(high_security::HighSecurityCommands),
73
74	/// Recovery commands
75	#[command(subcommand)]
76	Recovery(recovery::RecoveryCommands),
77
78	/// Scheduler commands
79	#[command(subcommand)]
80	Scheduler(scheduler::SchedulerCommands),
81
82	/// Direct interaction with chain storage (Sudo required for set)
83	#[command(subcommand)]
84	Storage(storage::StorageCommands),
85
86	/// Tech Collective management commands
87	#[command(subcommand)]
88	TechCollective(tech_collective::TechCollectiveCommands),
89
90	/// Runtime management commands (requires root/sudo permissions)
91	#[command(subcommand)]
92	Runtime(runtime::RuntimeCommands),
93
94	/// Generic extrinsic call - call ANY pallet function!
95	Call {
96		/// Pallet name (e.g., "Balances")
97		#[arg(long)]
98		pallet: String,
99
100		/// Call/function name (e.g., "transfer_allow_death")
101		#[arg(short, long)]
102		call: String,
103
104		/// Arguments as JSON array (e.g., '["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
105		/// "1000000000000"]')
106		#[arg(short, long)]
107		args: Option<String>,
108
109		/// Wallet name to sign with
110		#[arg(short, long)]
111		from: String,
112
113		/// Password for the wallet
114		#[arg(short, long)]
115		password: Option<String>,
116
117		/// Read password from file
118		#[arg(long)]
119		password_file: Option<String>,
120
121		/// Optional tip amount to prioritize the transaction
122		#[arg(long)]
123		tip: Option<String>,
124
125		/// Create offline extrinsic without submitting
126		#[arg(long)]
127		offline: bool,
128
129		/// Output the call as hex-encoded data only
130		#[arg(long)]
131		call_data_only: bool,
132	},
133
134	/// Query account balance
135	Balance {
136		/// Account address to query (SS58 format)
137		#[arg(short, long)]
138		address: String,
139	},
140
141	/// Developer utilities and testing tools
142	#[command(subcommand)]
143	Developer(DeveloperCommands),
144
145	/// Query events from blocks
146	Events {
147		/// Block number to query events from (full support)
148		#[arg(long)]
149		block: Option<u32>,
150
151		/// Block hash to query events from (full support)
152		#[arg(long)]
153		block_hash: Option<String>,
154
155		/// Query events from latest block
156		#[arg(long)]
157		latest: bool,
158
159		/// Query events from finalized block (full support)
160		#[arg(long)]
161		finalized: bool,
162
163		/// Filter events by pallet name (e.g., "Balances")
164		#[arg(long)]
165		pallet: Option<String>,
166
167		/// Show raw event data
168		#[arg(long)]
169		raw: bool,
170
171		/// Disable event decoding (decoding is enabled by default)
172		#[arg(long)]
173		no_decode: bool,
174	},
175
176	/// Query system information
177	System {
178		/// Show runtime version information
179		#[arg(long)]
180		runtime: bool,
181
182		/// Show metadata statistics
183		#[arg(long)]
184		metadata: bool,
185
186		/// Show available JSON-RPC methods exposed by the node
187		#[arg(long)]
188		rpc_methods: bool,
189	},
190
191	/// Explore chain metadata and available pallets/calls
192	Metadata {
193		/// Skip displaying documentation for calls
194		#[arg(long)]
195		no_docs: bool,
196
197		/// Show only metadata statistics
198		#[arg(long)]
199		stats_only: bool,
200
201		/// Filter by specific pallet name
202		#[arg(long)]
203		pallet: Option<String>,
204	},
205
206	/// Show version information
207	Version,
208
209	/// Check compatibility with the connected node
210	CompatibilityCheck,
211
212	/// Block management and analysis commands
213	#[command(subcommand)]
214	Block(block::BlockCommands),
215}
216
217/// Developer subcommands
218#[derive(Subcommand, Debug)]
219pub enum DeveloperCommands {
220	/// Create standard test wallets (crystal_alice, crystal_bob, crystal_charlie)
221	CreateTestWallets,
222}
223
224/// Execute a CLI command
225pub async fn execute_command(
226	command: Commands,
227	node_url: &str,
228	verbose: bool,
229) -> crate::error::Result<()> {
230	match command {
231		Commands::Wallet(wallet_cmd) => wallet::handle_wallet_command(wallet_cmd, node_url).await,
232		Commands::Send { from, to, amount, password, password_file, tip, nonce } =>
233			send::handle_send_command(
234				from,
235				to,
236				&amount,
237				node_url,
238				password,
239				password_file,
240				tip,
241				nonce,
242			)
243			.await,
244		Commands::Batch(batch_cmd) => batch::handle_batch_command(batch_cmd, node_url).await,
245		Commands::Reversible(reversible_cmd) =>
246			reversible::handle_reversible_command(reversible_cmd, node_url).await,
247		Commands::HighSecurity(hs_cmd) =>
248			high_security::handle_high_security_command(hs_cmd, node_url).await,
249		Commands::Recovery(recovery_cmd) =>
250			recovery::handle_recovery_command(recovery_cmd, node_url).await,
251		Commands::Scheduler(scheduler_cmd) =>
252			scheduler::handle_scheduler_command(scheduler_cmd, node_url).await,
253		Commands::Storage(storage_cmd) =>
254			storage::handle_storage_command(storage_cmd, node_url).await,
255		Commands::TechCollective(tech_collective_cmd) =>
256			tech_collective::handle_tech_collective_command(tech_collective_cmd, node_url).await,
257		Commands::Runtime(runtime_cmd) =>
258			runtime::handle_runtime_command(runtime_cmd, node_url).await,
259		Commands::Call {
260			pallet,
261			call,
262			args,
263			from,
264			password,
265			password_file,
266			tip,
267			offline,
268			call_data_only,
269		} =>
270			handle_generic_call_command(
271				pallet,
272				call,
273				args,
274				from,
275				password,
276				password_file,
277				tip,
278				offline,
279				call_data_only,
280				node_url,
281			)
282			.await,
283		Commands::Balance { address } => {
284			let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
285
286			// Resolve address (could be wallet name or SS58 address)
287			let resolved_address = common::resolve_address(&address)?;
288
289			let balance = send::get_balance(&quantus_client, &resolved_address).await?;
290			let formatted_balance =
291				send::format_balance_with_symbol(&quantus_client, balance).await?;
292			log_print!("๐Ÿ’ฐ Balance: {}", formatted_balance);
293			Ok(())
294		},
295		Commands::Developer(dev_cmd) => match dev_cmd {
296			DeveloperCommands::CreateTestWallets => {
297				let _ = crate::cli::handle_developer_command(DeveloperCommands::CreateTestWallets)
298					.await;
299				Ok(())
300			},
301		},
302		Commands::Events { block, block_hash, latest: _, finalized, pallet, raw, no_decode } =>
303			events::handle_events_command(
304				block, block_hash, finalized, pallet, raw, !no_decode, node_url,
305			)
306			.await,
307		Commands::System { runtime, metadata, rpc_methods } =>
308			if runtime || metadata || rpc_methods {
309				system::handle_system_extended_command(
310					node_url,
311					runtime,
312					metadata,
313					rpc_methods,
314					verbose,
315				)
316				.await
317			} else {
318				system::handle_system_command(node_url).await
319			},
320		Commands::Metadata { no_docs, stats_only, pallet } =>
321			metadata::handle_metadata_command(node_url, no_docs, stats_only, pallet).await,
322		Commands::Version => {
323			log_print!("CLI Version: Quantus CLI v{}", env!("CARGO_PKG_VERSION"));
324			Ok(())
325		},
326		Commands::CompatibilityCheck => handle_compatibility_check(node_url).await,
327		Commands::Block(block_cmd) => block::handle_block_command(block_cmd, node_url).await,
328	}
329}
330
331/// Handle generic extrinsic call command
332async fn handle_generic_call_command(
333	pallet: String,
334	call: String,
335	args: Option<String>,
336	from: String,
337	password: Option<String>,
338	password_file: Option<String>,
339	tip: Option<String>,
340	offline: bool,
341	call_data_only: bool,
342	node_url: &str,
343) -> crate::error::Result<()> {
344	// For now, we only support live submission (not offline or call-data-only)
345	if offline {
346		log_error!("โŒ Offline mode is not yet implemented");
347		log_print!("๐Ÿ’ก Currently only live submission is supported");
348		return Ok(());
349	}
350
351	if call_data_only {
352		log_error!("โŒ Call-data-only mode is not yet implemented");
353		log_print!("๐Ÿ’ก Currently only live submission is supported");
354		return Ok(());
355	}
356
357	let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
358
359	let args_vec = if let Some(args_str) = args {
360		serde_json::from_str(&args_str).map_err(|e| {
361			crate::error::QuantusError::Generic(format!("Invalid JSON for arguments: {e}"))
362		})?
363	} else {
364		vec![]
365	};
366
367	generic_call::handle_generic_call(&pallet, &call, args_vec, &keypair, tip, node_url).await
368}
369
370/// Handle developer subcommands
371pub async fn handle_developer_command(command: DeveloperCommands) -> crate::error::Result<()> {
372	match command {
373		DeveloperCommands::CreateTestWallets => {
374			use crate::wallet::WalletManager;
375
376			log_print!(
377				"๐Ÿงช {} Creating standard test wallets...",
378				"DEVELOPER".bright_magenta().bold()
379			);
380			log_print!("");
381
382			let wallet_manager = WalletManager::new()?;
383
384			// Standard test wallets with well-known names
385			let test_wallets = vec![
386				("crystal_alice", "Alice's test wallet for development"),
387				("crystal_bob", "Bob's test wallet for development"),
388				("crystal_charlie", "Charlie's test wallet for development"),
389			];
390
391			let mut created_count = 0;
392
393			for (name, description) in test_wallets {
394				log_verbose!("Creating wallet: {}", name.bright_green());
395
396				// Create wallet with a default password for testing
397				match wallet_manager.create_developer_wallet(name).await {
398					Ok(wallet_info) => {
399						log_success!("โœ… Created {}", name.bright_green());
400						log_success!("   Address: {}", wallet_info.address.bright_cyan());
401						log_success!("   Description: {}", description.dimmed());
402						created_count += 1;
403					},
404					Err(e) => {
405						log_error!("โŒ Failed to create {}: {}", name.bright_red(), e);
406					},
407				}
408			}
409
410			log_print!("");
411			log_success!("๐ŸŽ‰ Test wallet creation complete!");
412			log_success!("   Created: {} wallets", created_count.to_string().bright_green());
413			log_print!("");
414			log_print!("๐Ÿ’ก {} You can now use these wallets:", "TIP".bright_blue().bold());
415			log_print!("   quantus send --from crystal_alice --to <address> --amount 1000");
416			log_print!("   quantus send --from crystal_bob --to <address> --amount 1000");
417			log_print!("   quantus send --from crystal_charlie --to <address> --amount 1000");
418			log_print!("");
419
420			Ok(())
421		},
422	}
423}
424
425/// Handle compatibility check command
426async fn handle_compatibility_check(node_url: &str) -> crate::error::Result<()> {
427	log_print!("๐Ÿ” Compatibility Check");
428	log_print!("๐Ÿ”— Connecting to: {}", node_url.bright_cyan());
429	log_print!("");
430
431	// Connect to the node
432	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
433
434	// Get runtime version
435	let runtime_version = runtime::get_runtime_version(quantus_client.client()).await?;
436
437	// Get system info for additional details
438	let chain_info = system::get_complete_chain_info(node_url).await?;
439
440	log_print!("๐Ÿ“‹ Version Information:");
441	log_print!("   โ€ข CLI Version: {}", env!("CARGO_PKG_VERSION").bright_green());
442	log_print!(
443		"   โ€ข Runtime Spec Version: {}",
444		runtime_version.spec_version.to_string().bright_yellow()
445	);
446	log_print!(
447		"   โ€ข Runtime Impl Version: {}",
448		runtime_version.impl_version.to_string().bright_blue()
449	);
450	log_print!(
451		"   โ€ข Transaction Version: {}",
452		runtime_version.transaction_version.to_string().bright_magenta()
453	);
454
455	if let Some(name) = &chain_info.chain_name {
456		log_print!("   โ€ข Chain Name: {}", name.bright_cyan());
457	}
458
459	log_print!("");
460
461	// Check compatibility
462	let is_compatible = crate::config::is_runtime_compatible(runtime_version.spec_version);
463
464	log_print!("๐Ÿ” Compatibility Analysis:");
465	log_print!("   โ€ข Supported Runtime Versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
466	log_print!("   โ€ข Current Runtime Version: {}", runtime_version.spec_version);
467
468	if is_compatible {
469		log_success!("โœ… COMPATIBLE - This CLI version supports the connected node");
470		log_print!("   โ€ข All features should work correctly");
471		log_print!("   โ€ข You can safely use all CLI commands");
472	} else {
473		log_error!("โŒ INCOMPATIBLE - This CLI version may not work with the connected node");
474		log_print!("   โ€ข Some features may not work correctly");
475		log_print!("   โ€ข Consider updating the CLI or connecting to a compatible node");
476		log_print!("   โ€ข Supported versions: {:?}", crate::config::COMPATIBLE_RUNTIME_VERSIONS);
477	}
478
479	log_print!("");
480	log_print!("๐Ÿ’ก Tip: Use 'quantus version' for quick version check");
481	log_print!("๐Ÿ’ก Tip: Use 'quantus system --runtime' for detailed system info");
482
483	Ok(())
484}