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	finalized: bool,
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				finalized,
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				finalized,
74			)
75			.await?,
76
77		// System pallet calls
78		("System", "remark") =>
79			submit_system_remark(quantus_client, from_keypair, &args, tip_amount, finalized).await?,
80
81		// Sudo pallet calls
82		("Sudo", "sudo") => submit_sudo_call(quantus_client, from_keypair, &args).await?,
83
84		// TechCollective pallet calls
85		("TechCollective", "add_member") =>
86			submit_tech_collective_add_member(quantus_client, from_keypair, &args, finalized)
87				.await?,
88		("TechCollective", "remove_member") =>
89			submit_tech_collective_remove_member(quantus_client, from_keypair, &args, finalized)
90				.await?,
91		("TechCollective", "vote") =>
92			submit_tech_collective_vote(quantus_client, from_keypair, &args, finalized).await?,
93
94		// ReversibleTransfers pallet calls
95		("ReversibleTransfers", "schedule_transfer") =>
96			submit_reversible_transfer(quantus_client, from_keypair, &args, finalized).await?,
97
98		// Scheduler pallet calls
99		("Scheduler", "schedule") =>
100			submit_scheduler_schedule(quantus_client, from_keypair, &args).await?,
101		("Scheduler", "cancel") =>
102			submit_scheduler_cancel(quantus_client, from_keypair, &args).await?,
103
104		// Generic fallback for unknown calls
105		(_, _) => {
106			log_error!(
107				"❌ Pallet '{}' or call '{}' is not supported yet in SubXT implementation",
108				pallet,
109				call
110			);
111			log_print!("💡 Supported pallets in SubXT:");
112			log_print!("   • Balances: transfer_allow_death, transfer_keep_alive");
113			log_print!("   • System: remark");
114			log_print!("   • Sudo: sudo");
115			log_print!("   • TechCollective: add_member, remove_member, vote");
116			log_print!("   • ReversibleTransfers: schedule_transfer");
117			log_print!("   • Scheduler: schedule, cancel");
118			log_print!("💡 For other calls, use the original 'quantus call' command");
119			return Err(QuantusError::Generic(format!(
120				"Unsupported pallet/call combination in SubXT: {pallet}.{call}"
121			)));
122		},
123	};
124
125	log_success!("🎉 SubXT transaction submitted successfully!");
126	log_print!("📋 Transaction hash: {}", format!("0x{}", hex::encode(tx_hash)).bright_yellow());
127
128	Ok(tx_hash)
129}
130
131/// Submit balance transfer
132async fn submit_balance_transfer(
133	quantus_client: &crate::chain::client::QuantusClient,
134	from_keypair: &QuantumKeyPair,
135	args: &[Value],
136	keep_alive: bool,
137	tip: Option<u128>,
138	finalized: bool,
139) -> crate::error::Result<subxt::utils::H256> {
140	if args.len() != 2 {
141		return Err(QuantusError::Generic(
142			"Balances transfer requires 2 arguments: [to_address, amount]".to_string(),
143		));
144	}
145
146	let to_address = args[0].as_str().ok_or_else(|| {
147		QuantusError::Generic("First argument must be a string (to_address)".to_string())
148	})?;
149
150	let amount: u128 = args[1].as_str().unwrap_or("0").parse().map_err(|_| {
151		QuantusError::Generic("Second argument must be a number (amount)".to_string())
152	})?;
153
154	// Convert to AccountId32
155	let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
156		.map_err(|e| QuantusError::Generic(format!("Invalid to_address: {e:?}")))?;
157
158	// Convert to subxt_core AccountId32
159	let to_account_id_bytes: [u8; 32] = *to_account_id.as_ref();
160	let to_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
161
162	// Create and submit the transfer call
163	if keep_alive {
164		let transfer_call = quantus_subxt::api::tx().balances().transfer_keep_alive(
165			subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
166			amount,
167		);
168		crate::cli::common::submit_transaction(
169			quantus_client,
170			from_keypair,
171			transfer_call,
172			tip,
173			finalized,
174		)
175		.await
176	} else {
177		let transfer_call = quantus_subxt::api::tx().balances().transfer_allow_death(
178			subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
179			amount,
180		);
181		crate::cli::common::submit_transaction(
182			quantus_client,
183			from_keypair,
184			transfer_call,
185			tip,
186			finalized,
187		)
188		.await
189	}
190}
191
192/// Submit system remark
193async fn submit_system_remark(
194	quantus_client: &crate::chain::client::QuantusClient,
195	from_keypair: &QuantumKeyPair,
196	args: &[Value],
197	tip: Option<u128>,
198	finalized: bool,
199) -> crate::error::Result<subxt::utils::H256> {
200	if args.len() != 1 {
201		return Err(QuantusError::Generic(
202			"System remark requires 1 argument: [remark]".to_string(),
203		));
204	}
205
206	let remark = args[0]
207		.as_str()
208		.ok_or_else(|| QuantusError::Generic("Argument must be a string (remark)".to_string()))?;
209
210	let remark_call = quantus_subxt::api::tx().system().remark(remark.as_bytes().to_vec());
211
212	crate::cli::common::submit_transaction(
213		quantus_client,
214		from_keypair,
215		remark_call,
216		tip,
217		finalized,
218	)
219	.await
220}
221
222/// Submit sudo call
223async fn submit_sudo_call(
224	_quantus_client: &crate::chain::client::QuantusClient,
225	_from_keypair: &QuantumKeyPair,
226	_args: &[Value],
227) -> crate::error::Result<subxt::utils::H256> {
228	// For now, this is a placeholder - sudo calls need the inner call to be constructed
229	log_error!("❌ Sudo calls through generic call are complex - use specific sudo wrappers");
230	log_print!("💡 Use dedicated subxt commands that already wrap calls in sudo");
231	Err(QuantusError::Generic(
232		"Sudo calls not supported in generic call - use specific commands".to_string(),
233	))
234}
235
236/// Submit tech collective add member
237async fn submit_tech_collective_add_member(
238	quantus_client: &crate::chain::client::QuantusClient,
239	from_keypair: &QuantumKeyPair,
240	args: &[Value],
241	finalized: bool,
242) -> crate::error::Result<subxt::utils::H256> {
243	if args.len() != 1 {
244		return Err(QuantusError::Generic(
245			"TechCollective add_member requires 1 argument: [member_address]".to_string(),
246		));
247	}
248
249	let member_address = args[0].as_str().ok_or_else(|| {
250		QuantusError::Generic("Argument must be a string (member_address)".to_string())
251	})?;
252
253	let (member_account_id, _) = AccountId32::from_ss58check_with_version(member_address)
254		.map_err(|e| QuantusError::Generic(format!("Invalid member_address: {e:?}")))?;
255
256	// Convert to subxt_core AccountId32
257	let member_account_id_bytes: [u8; 32] = *member_account_id.as_ref();
258	let member_account_id_subxt =
259		subxt::ext::subxt_core::utils::AccountId32::from(member_account_id_bytes);
260
261	// Wrap in sudo for privileged operation
262	let sudo_call = quantus_subxt::api::tx().sudo().sudo(quantus_subxt::api::Call::TechCollective(
263		quantus_subxt::api::tech_collective::Call::add_member {
264			who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt),
265		},
266	));
267
268	crate::cli::common::submit_transaction(quantus_client, from_keypair, sudo_call, None, finalized)
269		.await
270}
271
272/// Submit tech collective remove member
273async fn submit_tech_collective_remove_member(
274	quantus_client: &crate::chain::client::QuantusClient,
275	from_keypair: &QuantumKeyPair,
276	args: &[Value],
277	finalized: bool,
278) -> crate::error::Result<subxt::utils::H256> {
279	if args.len() != 1 {
280		return Err(QuantusError::Generic(
281			"TechCollective remove_member requires 1 argument: [member_address]".to_string(),
282		));
283	}
284
285	let member_address = args[0].as_str().ok_or_else(|| {
286		QuantusError::Generic("Argument must be a string (member_address)".to_string())
287	})?;
288
289	let (member_account_id, _) = AccountId32::from_ss58check_with_version(member_address)
290		.map_err(|e| QuantusError::Generic(format!("Invalid member_address: {e:?}")))?;
291
292	// Convert to subxt_core AccountId32
293	let member_account_id_bytes: [u8; 32] = *member_account_id.as_ref();
294	let member_account_id_subxt =
295		subxt::ext::subxt_core::utils::AccountId32::from(member_account_id_bytes);
296
297	// Wrap in sudo for privileged operation
298	let sudo_call = quantus_subxt::api::tx().sudo().sudo(quantus_subxt::api::Call::TechCollective(
299		quantus_subxt::api::tech_collective::Call::remove_member {
300			who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id_subxt),
301			min_rank: 0, // Default rank
302		},
303	));
304
305	crate::cli::common::submit_transaction(quantus_client, from_keypair, sudo_call, None, finalized)
306		.await
307}
308
309/// Submit tech collective vote
310async fn submit_tech_collective_vote(
311	quantus_client: &crate::chain::client::QuantusClient,
312	from_keypair: &QuantumKeyPair,
313	args: &[Value],
314	finalized: bool,
315) -> crate::error::Result<subxt::utils::H256> {
316	if args.len() != 2 {
317		return Err(QuantusError::Generic(
318			"TechCollective vote requires 2 arguments: [referendum_index, aye]".to_string(),
319		));
320	}
321
322	let referendum_index: u32 = args[0].as_u64().unwrap_or(0) as u32;
323	let aye = args[1].as_bool().unwrap_or(false);
324
325	let vote_call = quantus_subxt::api::tx().tech_collective().vote(referendum_index, aye);
326
327	crate::cli::common::submit_transaction(quantus_client, from_keypair, vote_call, None, finalized)
328		.await
329}
330
331/// Submit reversible transfer
332async fn submit_reversible_transfer(
333	quantus_client: &crate::chain::client::QuantusClient,
334	from_keypair: &QuantumKeyPair,
335	args: &[Value],
336	finalized: bool,
337) -> crate::error::Result<subxt::utils::H256> {
338	if args.len() != 2 {
339		return Err(QuantusError::Generic(
340			"ReversibleTransfers schedule_transfer requires 2 arguments: [to_address, amount]"
341				.to_string(),
342		));
343	}
344
345	let to_address = args[0].as_str().ok_or_else(|| {
346		QuantusError::Generic("First argument must be a string (to_address)".to_string())
347	})?;
348
349	let amount: u128 = args[1].as_str().unwrap_or("0").parse().map_err(|_| {
350		QuantusError::Generic("Second argument must be a number (amount)".to_string())
351	})?;
352
353	let (to_account_id, _) = AccountId32::from_ss58check_with_version(to_address)
354		.map_err(|e| QuantusError::Generic(format!("Invalid to_address: {e:?}")))?;
355
356	// Convert to subxt_core AccountId32
357	let to_account_id_bytes: [u8; 32] = *to_account_id.as_ref();
358	let to_account_id_subxt = subxt::ext::subxt_core::utils::AccountId32::from(to_account_id_bytes);
359
360	let schedule_call = quantus_subxt::api::tx().reversible_transfers().schedule_transfer(
361		subxt::ext::subxt_core::utils::MultiAddress::Id(to_account_id_subxt),
362		amount,
363	);
364
365	crate::cli::common::submit_transaction(
366		quantus_client,
367		from_keypair,
368		schedule_call,
369		None,
370		finalized,
371	)
372	.await
373}
374
375/// Submit scheduler schedule
376async fn submit_scheduler_schedule(
377	_quantus_client: &crate::chain::client::QuantusClient,
378	_from_keypair: &QuantumKeyPair,
379	_args: &[Value],
380) -> crate::error::Result<subxt::utils::H256> {
381	log_error!("❌ Scheduler calls through generic call are complex");
382	log_print!("💡 Use dedicated scheduler commands for complex scheduling");
383	Err(QuantusError::Generic(
384		"Scheduler calls not supported in generic call - use scheduler commands".to_string(),
385	))
386}
387
388/// Submit scheduler cancel
389async fn submit_scheduler_cancel(
390	_quantus_client: &crate::chain::client::QuantusClient,
391	_from_keypair: &QuantumKeyPair,
392	_args: &[Value],
393) -> crate::error::Result<subxt::utils::H256> {
394	log_error!("❌ Scheduler calls through generic call are complex");
395	log_print!("💡 Use dedicated scheduler commands for scheduling operations");
396	Err(QuantusError::Generic(
397		"Scheduler calls not supported in generic call - use scheduler commands".to_string(),
398	))
399}
400
401/// Handle generic call command execution
402pub async fn handle_generic_call(
403	pallet: &str,
404	call: &str,
405	args: Vec<Value>,
406	keypair: &QuantumKeyPair,
407	tip: Option<String>,
408	node_url: &str,
409	finalized: bool,
410) -> crate::error::Result<()> {
411	log_print!("🚀 Generic Call");
412
413	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
414
415	execute_generic_call(&quantus_client, pallet, call, args, keypair, tip, finalized).await?;
416
417	Ok(())
418}