Skip to main content

quantus_cli/cli/
generic_call.rs

1//! `quantus call` subcommand - generic pallet calls
2use crate::{
3	chain::quantus_subxt, error::QuantusError, log_error, log_print, log_success, log_verbose,
4	wallet::QuantumKeyPair,
5};
6use colored::Colorize;
7use serde_json::Value;
8use sp_core::crypto::{AccountId32, Ss58Codec};
9
10/// Execute a generic call to any pallet
11pub async fn execute_generic_call(
12	quantus_client: &crate::chain::client::QuantusClient,
13	pallet: &str,
14	call: &str,
15	args: Vec<Value>,
16	from_keypair: &QuantumKeyPair,
17	tip: Option<String>,
18	execution_mode: crate::cli::common::ExecutionMode,
19) -> crate::error::Result<subxt::utils::H256> {
20	log_print!("🚀 Executing generic call");
21	log_print!("Pallet: {}", pallet.bright_green());
22	log_print!("Call: {}", call.bright_cyan());
23	log_print!("From: {}", from_keypair.to_account_id_ss58check().bright_yellow());
24	if let Some(tip) = &tip {
25		log_print!("Tip: {}", tip.bright_magenta());
26	}
27
28	// Convert our QuantumKeyPair to subxt Signer
29	let _signer = from_keypair
30		.to_subxt_signer()
31		.map_err(|e| QuantusError::NetworkError(format!("Failed to convert keypair: {e:?}")))?;
32
33	// Validate pallet/call exists in metadata
34	let metadata = quantus_client.client().metadata();
35	let pallet_metadata = metadata
36		.pallet_by_name(pallet)
37		.ok_or_else(|| QuantusError::Generic(format!("Pallet '{pallet}' not found in metadata")))?;
38
39	log_verbose!("✅ Found pallet '{}' with index {}", pallet, pallet_metadata.index());
40
41	// Find the call in the pallet
42	let call_metadata = pallet_metadata.call_variant_by_name(call).ok_or_else(|| {
43		QuantusError::Generic(format!("Call '{call}' not found in pallet '{pallet}'"))
44	})?;
45
46	log_verbose!("✅ Found call '{}' with index {}", call, call_metadata.index);
47
48	// Parse tip amount if provided
49	let tip_amount = if let Some(tip_str) = &tip { tip_str.parse::<u128>().ok() } else { None };
50
51	// Create and submit extrinsic based on pallet and call
52	log_print!("🔧 Creating extrinsic for {}.{}", pallet, call);
53
54	let tx_hash = match (pallet, call) {
55		// Balances pallet calls
56		("Balances", "transfer_allow_death") =>
57			submit_balance_transfer(
58				quantus_client,
59				from_keypair,
60				&args,
61				false,
62				tip_amount,
63				execution_mode,
64			)
65			.await?,
66		("Balances", "transfer_keep_alive") =>
67			submit_balance_transfer(
68				quantus_client,
69				from_keypair,
70				&args,
71				true,
72				tip_amount,
73				execution_mode,
74			)
75			.await?,
76
77		// System pallet calls
78		("System", "remark") =>
79			submit_system_remark(quantus_client, from_keypair, &args, tip_amount, execution_mode)
80				.await?,
81
82		// Sudo pallet calls
83		("Sudo", "sudo") => submit_sudo_call(quantus_client, from_keypair, &args).await?,
84
85		// TechCollective pallet calls
86		("TechCollective", "add_member") =>
87			submit_tech_collective_add_member(quantus_client, from_keypair, &args, execution_mode)
88				.await?,
89		("TechCollective", "remove_member") =>
90			submit_tech_collective_remove_member(
91				quantus_client,
92				from_keypair,
93				&args,
94				execution_mode,
95			)
96			.await?,
97		("TechCollective", "vote") =>
98			submit_tech_collective_vote(quantus_client, from_keypair, &args, execution_mode).await?,
99
100		// ReversibleTransfers pallet calls
101		("ReversibleTransfers", "schedule_transfer") =>
102			submit_reversible_transfer(quantus_client, from_keypair, &args, execution_mode).await?,
103
104		// Scheduler pallet calls
105		("Scheduler", "schedule") =>
106			submit_scheduler_schedule(quantus_client, from_keypair, &args).await?,
107		("Scheduler", "cancel") =>
108			submit_scheduler_cancel(quantus_client, from_keypair, &args).await?,
109
110		// Generic fallback for unknown calls
111		(_, _) => {
112			log_error!(
113				"❌ Pallet '{}' or call '{}' is not supported yet in SubXT implementation",
114				pallet,
115				call
116			);
117			log_print!("💡 Supported pallets in SubXT:");
118			log_print!("   • Balances: transfer_allow_death, transfer_keep_alive");
119			log_print!("   • System: remark");
120			log_print!("   • Sudo: sudo");
121			log_print!("   • TechCollective: add_member, remove_member, vote");
122			log_print!("   • ReversibleTransfers: schedule_transfer");
123			log_print!("   • Scheduler: schedule, cancel");
124			log_print!("💡 For other calls, use the original 'quantus call' command");
125			return Err(QuantusError::Generic(format!(
126				"Unsupported pallet/call combination in SubXT: {pallet}.{call}"
127			)));
128		},
129	};
130
131	log_success!("🎉 SubXT transaction submitted successfully!");
132	log_print!("📋 Transaction hash: {}", format!("0x{}", hex::encode(tx_hash)).bright_yellow());
133
134	Ok(tx_hash)
135}
136
137/// Submit balance transfer
138async fn submit_balance_transfer(
139	quantus_client: &crate::chain::client::QuantusClient,
140	from_keypair: &QuantumKeyPair,
141	args: &[Value],
142	keep_alive: bool,
143	tip: Option<u128>,
144	execution_mode: crate::cli::common::ExecutionMode,
145) -> crate::error::Result<subxt::utils::H256> {
146	if args.len() != 2 {
147		return Err(QuantusError::Generic(
148			"Balances transfer requires 2 arguments: [to_address, amount]".to_string(),
149		));
150	}
151
152	let to_address = args[0].as_str().ok_or_else(|| {
153		QuantusError::Generic("First argument must be a string (to_address)".to_string())
154	})?;
155
156	let amount: u128 = args[1].as_str().unwrap_or("0").parse().map_err(|_| {
157		QuantusError::Generic("Second argument must be a number (amount)".to_string())
158	})?;
159
160	// Convert to AccountId32
161	let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
162		.map_err(|e| QuantusError::Generic(format!("Invalid to_address: {e:?}")))?;
163
164	// Convert to subxt_core AccountId32
165	let to_account_id_bytes: [u8; 32] = *to_account_id.as_ref();
166	let to_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
167
168	// Create and submit the transfer call
169	if keep_alive {
170		let transfer_call = quantus_subxt::api::tx().balances().transfer_keep_alive(
171			subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
172			amount,
173		);
174		crate::cli::common::submit_transaction(
175			quantus_client,
176			from_keypair,
177			transfer_call,
178			tip,
179			execution_mode,
180		)
181		.await
182	} else {
183		let transfer_call = quantus_subxt::api::tx().balances().transfer_allow_death(
184			subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
185			amount,
186		);
187		crate::cli::common::submit_transaction(
188			quantus_client,
189			from_keypair,
190			transfer_call,
191			tip,
192			execution_mode,
193		)
194		.await
195	}
196}
197
198/// Submit system remark
199async fn submit_system_remark(
200	quantus_client: &crate::chain::client::QuantusClient,
201	from_keypair: &QuantumKeyPair,
202	args: &[Value],
203	tip: Option<u128>,
204	execution_mode: crate::cli::common::ExecutionMode,
205) -> crate::error::Result<subxt::utils::H256> {
206	if args.len() != 1 {
207		return Err(QuantusError::Generic(
208			"System remark requires 1 argument: [remark]".to_string(),
209		));
210	}
211
212	let remark = args[0]
213		.as_str()
214		.ok_or_else(|| QuantusError::Generic("Argument must be a string (remark)".to_string()))?;
215
216	let remark_call = quantus_subxt::api::tx().system().remark(remark.as_bytes().to_vec());
217
218	crate::cli::common::submit_transaction(
219		quantus_client,
220		from_keypair,
221		remark_call,
222		tip,
223		execution_mode,
224	)
225	.await
226}
227
228/// Submit sudo call
229async fn submit_sudo_call(
230	_quantus_client: &crate::chain::client::QuantusClient,
231	_from_keypair: &QuantumKeyPair,
232	_args: &[Value],
233) -> crate::error::Result<subxt::utils::H256> {
234	// For now, this is a placeholder - sudo calls need the inner call to be constructed
235	log_error!("❌ Sudo calls through generic call are complex - use specific sudo wrappers");
236	log_print!("💡 Use dedicated subxt commands that already wrap calls in sudo");
237	Err(QuantusError::Generic(
238		"Sudo calls not supported in generic call - use specific commands".to_string(),
239	))
240}
241
242/// Submit tech collective add member
243async fn submit_tech_collective_add_member(
244	quantus_client: &crate::chain::client::QuantusClient,
245	from_keypair: &QuantumKeyPair,
246	args: &[Value],
247	execution_mode: crate::cli::common::ExecutionMode,
248) -> crate::error::Result<subxt::utils::H256> {
249	if args.len() != 1 {
250		return Err(QuantusError::Generic(
251			"TechCollective add_member requires 1 argument: [member_address]".to_string(),
252		));
253	}
254
255	let member_address = args[0].as_str().ok_or_else(|| {
256		QuantusError::Generic("Argument must be a string (member_address)".to_string())
257	})?;
258
259	let (member_account_id, _) = AccountId32::from_ss58check_with_version(member_address)
260		.map_err(|e| QuantusError::Generic(format!("Invalid member_address: {e:?}")))?;
261
262	// Convert to subxt_core AccountId32
263	let member_account_id_bytes: [u8; 32] = *member_account_id.as_ref();
264	let member_account_id_subxt =
265		subxt::ext::subxt_core::utils::AccountId32::from(member_account_id_bytes);
266
267	// Wrap in sudo for privileged operation
268	let sudo_call = quantus_subxt::api::tx().sudo().sudo(quantus_subxt::api::Call::TechCollective(
269		quantus_subxt::api::tech_collective::Call::add_member {
270			who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt),
271		},
272	));
273
274	crate::cli::common::submit_transaction(
275		quantus_client,
276		from_keypair,
277		sudo_call,
278		None,
279		execution_mode,
280	)
281	.await
282}
283
284/// Submit tech collective remove member
285async fn submit_tech_collective_remove_member(
286	quantus_client: &crate::chain::client::QuantusClient,
287	from_keypair: &QuantumKeyPair,
288	args: &[Value],
289	execution_mode: crate::cli::common::ExecutionMode,
290) -> crate::error::Result<subxt::utils::H256> {
291	if args.len() != 1 {
292		return Err(QuantusError::Generic(
293			"TechCollective remove_member requires 1 argument: [member_address]".to_string(),
294		));
295	}
296
297	let member_address = args[0].as_str().ok_or_else(|| {
298		QuantusError::Generic("Argument must be a string (member_address)".to_string())
299	})?;
300
301	let (member_account_id, _) = AccountId32::from_ss58check_with_version(member_address)
302		.map_err(|e| QuantusError::Generic(format!("Invalid member_address: {e:?}")))?;
303
304	// Convert to subxt_core AccountId32
305	let member_account_id_bytes: [u8; 32] = *member_account_id.as_ref();
306	let member_account_id_subxt =
307		subxt::ext::subxt_core::utils::AccountId32::from(member_account_id_bytes);
308
309	// Wrap in sudo for privileged operation
310	let sudo_call = quantus_subxt::api::tx().sudo().sudo(quantus_subxt::api::Call::TechCollective(
311		quantus_subxt::api::tech_collective::Call::remove_member {
312			who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt),
313			min_rank: 0, // Default rank
314		},
315	));
316
317	crate::cli::common::submit_transaction(
318		quantus_client,
319		from_keypair,
320		sudo_call,
321		None,
322		execution_mode,
323	)
324	.await
325}
326
327/// Submit tech collective vote
328async fn submit_tech_collective_vote(
329	quantus_client: &crate::chain::client::QuantusClient,
330	from_keypair: &QuantumKeyPair,
331	args: &[Value],
332	execution_mode: crate::cli::common::ExecutionMode,
333) -> crate::error::Result<subxt::utils::H256> {
334	if args.len() != 2 {
335		return Err(QuantusError::Generic(
336			"TechCollective vote requires 2 arguments: [referendum_index, aye]".to_string(),
337		));
338	}
339
340	let referendum_index: u32 = args[0].as_u64().unwrap_or(0) as u32;
341	let aye = args[1].as_bool().unwrap_or(false);
342
343	let vote_call = quantus_subxt::api::tx().tech_collective().vote(referendum_index, aye);
344
345	crate::cli::common::submit_transaction(
346		quantus_client,
347		from_keypair,
348		vote_call,
349		None,
350		execution_mode,
351	)
352	.await
353}
354
355/// Submit reversible transfer
356async fn submit_reversible_transfer(
357	quantus_client: &crate::chain::client::QuantusClient,
358	from_keypair: &QuantumKeyPair,
359	args: &[Value],
360	execution_mode: crate::cli::common::ExecutionMode,
361) -> crate::error::Result<subxt::utils::H256> {
362	if args.len() != 2 {
363		return Err(QuantusError::Generic(
364			"ReversibleTransfers schedule_transfer requires 2 arguments: [to_address, amount]"
365				.to_string(),
366		));
367	}
368
369	let to_address = args[0].as_str().ok_or_else(|| {
370		QuantusError::Generic("First argument must be a string (to_address)".to_string())
371	})?;
372
373	let amount: u128 = args[1].as_str().unwrap_or("0").parse().map_err(|_| {
374		QuantusError::Generic("Second argument must be a number (amount)".to_string())
375	})?;
376
377	let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
378		.map_err(|e| QuantusError::Generic(format!("Invalid to_address: {e:?}")))?;
379
380	// Convert to subxt_core AccountId32
381	let to_account_id_bytes: [u8; 32] = *to_account_id.as_ref();
382	let to_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
383
384	let schedule_call = quantus_subxt::api::tx().reversible_transfers().schedule_transfer(
385		subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
386		amount,
387	);
388
389	crate::cli::common::submit_transaction(
390		quantus_client,
391		from_keypair,
392		schedule_call,
393		None,
394		execution_mode,
395	)
396	.await
397}
398
399/// Submit scheduler schedule
400async fn submit_scheduler_schedule(
401	_quantus_client: &crate::chain::client::QuantusClient,
402	_from_keypair: &QuantumKeyPair,
403	_args: &[Value],
404) -> crate::error::Result<subxt::utils::H256> {
405	log_error!("❌ Scheduler calls through generic call are complex");
406	log_print!("💡 Use dedicated scheduler commands for complex scheduling");
407	Err(QuantusError::Generic(
408		"Scheduler calls not supported in generic call - use scheduler commands".to_string(),
409	))
410}
411
412/// Submit scheduler cancel
413async fn submit_scheduler_cancel(
414	_quantus_client: &crate::chain::client::QuantusClient,
415	_from_keypair: &QuantumKeyPair,
416	_args: &[Value],
417) -> crate::error::Result<subxt::utils::H256> {
418	log_error!("❌ Scheduler calls through generic call are complex");
419	log_print!("💡 Use dedicated scheduler commands for scheduling operations");
420	Err(QuantusError::Generic(
421		"Scheduler calls not supported in generic call - use scheduler commands".to_string(),
422	))
423}
424
425/// Handle generic call command execution
426pub async fn handle_generic_call(
427	pallet: &str,
428	call: &str,
429	args: Vec<Value>,
430	keypair: &QuantumKeyPair,
431	tip: Option<String>,
432	node_url: &str,
433	execution_mode: crate::cli::common::ExecutionMode,
434) -> crate::error::Result<()> {
435	log_print!("🚀 Generic Call");
436
437	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
438
439	execute_generic_call(&quantus_client, pallet, call, args, keypair, tip, execution_mode).await?;
440
441	Ok(())
442}