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