quantus_cli/cli/
batch.rs

1//! Batch transfer commands and configuration
2use crate::{
3	chain::client::QuantusClient,
4	cli::send::{
5		batch_transfer, get_batch_limits, load_transfers_from_file, validate_and_format_amount,
6	},
7	error::Result,
8	log_error, log_info, log_print, log_success,
9};
10use clap::Subcommand;
11use colored::Colorize;
12
13#[derive(Subcommand, Debug)]
14pub enum BatchCommands {
15	/// Send tokens to multiple recipients in a single batch transaction
16	Send {
17		/// Wallet name to send from
18		#[arg(short, long)]
19		from: String,
20
21		/// Password for the wallet (or use environment variables)
22		#[arg(short, long)]
23		password: Option<String>,
24
25		/// Read password from file (for scripting)
26		#[arg(long)]
27		password_file: Option<String>,
28
29		/// Optional tip amount to prioritize the transaction (e.g., "1", "0.5")
30		#[arg(long)]
31		tip: Option<String>,
32
33		/// Batch file with transfers (JSON format: [{"to": "address", "amount": "1000"}, ...])
34		#[arg(long)]
35		batch_file: Option<String>,
36
37		/// Number of identical transfers to generate for testing
38		#[arg(long)]
39		count: Option<u32>,
40
41		/// Recipient address for generated transfers (required with --count)
42		#[arg(long)]
43		to: Option<String>,
44
45		/// Amount per transfer for generated transfers (required with --count)
46		#[arg(long)]
47		amount: Option<String>,
48	},
49
50	/// Configuration and limits for batch transfers
51	Config {
52		/// Show current batch transfer limits for the connected chain
53		#[arg(long)]
54		limits: bool,
55
56		/// Show batch transfer configuration and recommendations
57		#[arg(long)]
58		info: bool,
59	},
60}
61
62/// Handle batch commands
63pub async fn handle_batch_command(command: BatchCommands, node_url: &str) -> Result<()> {
64	match command {
65		BatchCommands::Send {
66			from,
67			password,
68			password_file,
69			tip,
70			batch_file,
71			count,
72			to,
73			amount,
74		} =>
75			handle_batch_send_command(
76				from,
77				node_url,
78				password,
79				password_file,
80				tip,
81				batch_file,
82				count,
83				to,
84				amount,
85			)
86			.await,
87		BatchCommands::Config { limits, info } =>
88			handle_batch_config_command(node_url, limits, info).await,
89	}
90}
91
92/// Handle the batch send command (moved from send.rs)
93async fn handle_batch_send_command(
94	from_wallet: String,
95	node_url: &str,
96	password: Option<String>,
97	password_file: Option<String>,
98	tip: Option<String>,
99	batch_file: Option<String>,
100	count: Option<u32>,
101	to: Option<String>,
102	amount: Option<String>,
103) -> Result<()> {
104	// Create quantus chain client
105	let quantus_client = QuantusClient::new(node_url).await?;
106
107	// Prepare transfers list
108	let transfers = if let Some(file_path) = batch_file {
109		// Load from JSON file
110		load_transfers_from_file(&file_path).await?
111	} else if let (Some(count_val), Some(to_addr), Some(amount_str)) = (count, to, amount) {
112		// Generate identical transfers
113		let (parsed_amount, _) = validate_and_format_amount(&quantus_client, &amount_str).await?;
114		let mut transfers = Vec::new();
115		for _ in 0..count_val {
116			transfers.push((to_addr.clone(), parsed_amount));
117		}
118		transfers
119	} else {
120		return Err(crate::error::QuantusError::Generic(
121			"Either --batch-file or (--count + --to + --amount) must be provided".to_string(),
122		));
123	};
124
125	if transfers.is_empty() {
126		return Err(crate::error::QuantusError::Generic("No transfers to process".to_string()));
127	}
128
129	log_info!("🚀 Initiating batch transfer with {} transfers", transfers.len());
130
131	// Parse tip if provided
132	let tip_amount = if let Some(tip_str) = tip {
133		let (tip_val, _) = validate_and_format_amount(&quantus_client, &tip_str).await?;
134		Some(tip_val)
135	} else {
136		None
137	};
138
139	// Load wallet
140	let keypair = crate::wallet::load_keypair_from_wallet(&from_wallet, password, password_file)?;
141	let from_account_id = keypair.to_account_id_ss58check();
142
143	// Check balance
144	let balance = crate::cli::send::get_balance(&quantus_client, &from_account_id).await?;
145	let total_amount: u128 = transfers.iter().map(|(_, amount)| amount).sum();
146	let estimated_fee = 50_000_000_000u128; // Rough estimate for batch
147
148	if balance < total_amount + estimated_fee {
149		let formatted_balance =
150			crate::cli::send::format_balance_with_symbol(&quantus_client, balance).await?;
151		let formatted_needed = crate::cli::send::format_balance_with_symbol(
152			&quantus_client,
153			total_amount + estimated_fee,
154		)
155		.await?;
156		return Err(crate::error::QuantusError::Generic(format!(
157			"Insufficient balance. Have: {formatted_balance}, Need: {formatted_needed} (including estimated fees)"
158		)));
159	}
160
161	// Submit batch transaction
162	let tx_hash = batch_transfer(&quantus_client, &keypair, transfers, tip_amount).await?;
163
164	log_print!(
165		"✅ {} Batch transaction submitted! Hash: {:?}",
166		"SUCCESS".bright_green().bold(),
167		tx_hash
168	);
169
170	let success =
171		crate::cli::progress_spinner::wait_for_tx_confirmation(quantus_client.client(), tx_hash)
172			.await?;
173
174	if success {
175		log_info!("✅ Batch transaction confirmed and finalized on chain");
176		log_success!("🎉 {} Batch transaction confirmed!", "FINISHED".bright_green().bold());
177
178		// Show updated balance
179		let new_balance = crate::cli::send::get_balance(&quantus_client, &from_account_id).await?;
180		let formatted_new_balance =
181			crate::cli::send::format_balance_with_symbol(&quantus_client, new_balance).await?;
182		log_print!("💰 New balance: {}", formatted_new_balance.bright_yellow());
183	} else {
184		log_error!("Batch transaction failed!");
185	}
186
187	Ok(())
188}
189
190/// Handle batch config command
191async fn handle_batch_config_command(
192	node_url: &str,
193	show_limits: bool,
194	show_info: bool,
195) -> Result<()> {
196	let quantus_client = QuantusClient::new(node_url).await?;
197
198	if show_limits {
199		log_info!("🔍 Checking batch transfer limits for chain...");
200
201		let (safe_limit, recommended_limit) = get_batch_limits(&quantus_client).await?;
202
203		log_print!("📊 {} Batch Transfer Limits", "CHAIN".bright_cyan().bold());
204		log_print!("   â€ĸ Safe batch size: {} transfers", safe_limit.to_string().bright_green());
205		log_print!(
206			"   â€ĸ Maximum batch size: {} transfers",
207			recommended_limit.to_string().bright_yellow()
208		);
209		log_print!("   â€ĸ For larger batches, split into multiple transactions");
210	}
211
212	if show_info {
213		log_print!("â„šī¸  {} Batch Transfer Information", "CONFIG".bright_cyan().bold());
214		log_print!("   â€ĸ Batch transfers use utility.batch() pallet");
215		log_print!("   â€ĸ All transfers in one transaction (atomic)");
216		log_print!("   â€ĸ Single nonce used for all transfers");
217		log_print!("   â€ĸ Lower fees compared to individual transfers");
218		log_print!("   â€ĸ If one transfer fails, entire batch fails");
219		log_print!("");
220		log_print!("📝 {} Usage Examples", "EXAMPLES".bright_cyan().bold());
221		log_print!("   quantus batch send --from alice --count 100 --to bob --amount 1000");
222		log_print!("   quantus batch send --from alice --batch-file transfers.json");
223		log_print!("   quantus batch config --limits");
224	}
225
226	// If no flags provided, show both
227	if !show_limits && !show_info {
228		// Avoid recursion by calling the logic directly
229		let quantus_client = QuantusClient::new(node_url).await?;
230
231		// Show limits
232		log_info!("🔍 Checking batch transfer limits for chain...");
233		let (safe_limit, recommended_limit) = get_batch_limits(&quantus_client).await?;
234		log_print!("📊 {} Batch Transfer Limits", "CHAIN".bright_cyan().bold());
235		log_print!("   â€ĸ Safe batch size: {} transfers", safe_limit.to_string().bright_green());
236		log_print!(
237			"   â€ĸ Maximum batch size: {} transfers",
238			recommended_limit.to_string().bright_yellow()
239		);
240		log_print!("   â€ĸ For larger batches, split into multiple transactions");
241
242		// Show info
243		log_print!("â„šī¸  {} Batch Transfer Information", "CONFIG".bright_cyan().bold());
244		log_print!("   â€ĸ Batch transfers use utility.batch() pallet");
245		log_print!("   â€ĸ All transfers in one transaction (atomic)");
246		log_print!("   â€ĸ Single nonce used for all transfers");
247		log_print!("   â€ĸ Lower fees compared to individual transfers");
248		log_print!("   â€ĸ If one transfer fails, entire batch fails");
249		log_print!("");
250		log_print!("📝 {} Usage Examples", "EXAMPLES".bright_cyan().bold());
251		log_print!("   quantus batch send --from alice --count 100 --to bob --amount 1000");
252		log_print!("   quantus batch send --from alice --batch-file transfers.json");
253		log_print!("   quantus batch config --limits");
254	}
255
256	Ok(())
257}