quantus_cli/cli/
referenda.rs

1//! `quantus referenda` subcommand - manage standard Referenda proposals
2use crate::{
3	chain::quantus_subxt, cli::common::submit_transaction, error::QuantusError, log_error,
4	log_print, log_success, log_verbose,
5};
6use clap::Subcommand;
7use colored::Colorize;
8use std::str::FromStr;
9
10/// Standard Referenda management commands
11#[derive(Subcommand, Debug)]
12pub enum ReferendaCommands {
13	/// Submit a simple proposal (System::remark) to test Referenda
14	SubmitRemark {
15		/// Message to include in the remark
16		#[arg(long)]
17		message: String,
18
19		/// Wallet name to sign with
20		#[arg(short, long)]
21		from: String,
22
23		/// Password for the wallet
24		#[arg(short, long)]
25		password: Option<String>,
26
27		/// Read password from file
28		#[arg(long)]
29		password_file: Option<String>,
30
31		/// Origin type: signed (default), none (for signaling track), root
32		#[arg(long, default_value = "signed")]
33		origin: String,
34	},
35
36	/// Submit a proposal using existing preimage hash
37	Submit {
38		/// Preimage hash (must already exist on chain)
39		#[arg(long)]
40		preimage_hash: String,
41
42		/// Wallet name to sign with
43		#[arg(short, long)]
44		from: String,
45
46		/// Password for the wallet
47		#[arg(short, long)]
48		password: Option<String>,
49
50		/// Read password from file
51		#[arg(long)]
52		password_file: Option<String>,
53
54		/// Origin type: signed (default), none (for signaling track), root
55		#[arg(long, default_value = "signed")]
56		origin: String,
57	},
58
59	/// List all active Referenda proposals
60	List,
61
62	/// Get details of a specific Referendum
63	Get {
64		/// Referendum index
65		#[arg(short, long)]
66		index: u32,
67
68		/// Decode and display the proposal call in human-readable format
69		#[arg(long)]
70		decode: bool,
71	},
72
73	/// Check the status of a Referendum
74	Status {
75		/// Referendum index
76		#[arg(short, long)]
77		index: u32,
78	},
79
80	/// Place a decision deposit for a Referendum
81	PlaceDecisionDeposit {
82		/// Referendum index
83		#[arg(short, long)]
84		index: u32,
85
86		/// Wallet name to sign with
87		#[arg(short, long)]
88		from: String,
89
90		/// Password for the wallet
91		#[arg(short, long)]
92		password: Option<String>,
93
94		/// Read password from file
95		#[arg(long)]
96		password_file: Option<String>,
97	},
98
99	/// Vote on a Referendum (uses conviction voting)
100	Vote {
101		/// Referendum index
102		#[arg(short, long)]
103		index: u32,
104
105		/// Vote aye (true) or nay (false)
106		#[arg(long)]
107		aye: bool,
108
109		/// Conviction (0=None, 1=Locked1x, 2=Locked2x, up to 6=Locked6x)
110		#[arg(long, default_value = "0")]
111		conviction: u8,
112
113		/// Amount to vote with
114		#[arg(long)]
115		amount: String,
116
117		/// Wallet name to sign with
118		#[arg(short, long)]
119		from: String,
120
121		/// Password for the wallet
122		#[arg(short, long)]
123		password: Option<String>,
124
125		/// Read password from file
126		#[arg(long)]
127		password_file: Option<String>,
128	},
129
130	/// Refund submission deposit for a completed Referendum
131	RefundSubmissionDeposit {
132		/// Referendum index
133		#[arg(short, long)]
134		index: u32,
135
136		/// Wallet name that submitted the referendum
137		#[arg(short, long)]
138		from: String,
139
140		/// Password for the wallet
141		#[arg(short, long)]
142		password: Option<String>,
143
144		/// Read password from file
145		#[arg(long)]
146		password_file: Option<String>,
147	},
148
149	/// Refund decision deposit for a completed Referendum
150	RefundDecisionDeposit {
151		/// Referendum index
152		#[arg(short, long)]
153		index: u32,
154
155		/// Wallet name that placed the decision deposit
156		#[arg(short, long)]
157		from: String,
158
159		/// Password for the wallet
160		#[arg(short, long)]
161		password: Option<String>,
162
163		/// Read password from file
164		#[arg(long)]
165		password_file: Option<String>,
166	},
167
168	/// Get Referenda configuration
169	Config,
170}
171
172/// Handle referenda commands
173pub async fn handle_referenda_command(
174	command: ReferendaCommands,
175	node_url: &str,
176	finalized: bool,
177) -> crate::error::Result<()> {
178	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
179
180	match command {
181		ReferendaCommands::SubmitRemark { message, from, password, password_file, origin } =>
182			submit_remark_proposal(
183				&quantus_client,
184				&message,
185				&from,
186				password,
187				password_file,
188				&origin,
189				finalized,
190			)
191			.await,
192		ReferendaCommands::Submit { preimage_hash, from, password, password_file, origin } =>
193			submit_proposal(
194				&quantus_client,
195				&preimage_hash,
196				&from,
197				password,
198				password_file,
199				&origin,
200				finalized,
201			)
202			.await,
203		ReferendaCommands::List => list_proposals(&quantus_client).await,
204		ReferendaCommands::Get { index, decode } =>
205			get_proposal_details(&quantus_client, index, decode).await,
206		ReferendaCommands::Status { index } => get_proposal_status(&quantus_client, index).await,
207		ReferendaCommands::PlaceDecisionDeposit { index, from, password, password_file } =>
208			place_decision_deposit(
209				&quantus_client,
210				index,
211				&from,
212				password,
213				password_file,
214				finalized,
215			)
216			.await,
217		ReferendaCommands::Vote {
218			index,
219			aye,
220			conviction,
221			amount,
222			from,
223			password,
224			password_file,
225		} =>
226			vote_on_referendum(
227				&quantus_client,
228				index,
229				aye,
230				conviction,
231				&amount,
232				&from,
233				password,
234				password_file,
235				finalized,
236			)
237			.await,
238		ReferendaCommands::RefundSubmissionDeposit { index, from, password, password_file } =>
239			refund_submission_deposit(
240				&quantus_client,
241				index,
242				&from,
243				password,
244				password_file,
245				finalized,
246			)
247			.await,
248		ReferendaCommands::RefundDecisionDeposit { index, from, password, password_file } =>
249			refund_decision_deposit(
250				&quantus_client,
251				index,
252				&from,
253				password,
254				password_file,
255				finalized,
256			)
257			.await,
258		ReferendaCommands::Config => get_config(&quantus_client).await,
259	}
260}
261
262/// Submit a simple System::remark proposal
263async fn submit_remark_proposal(
264	quantus_client: &crate::chain::client::QuantusClient,
265	message: &str,
266	from: &str,
267	password: Option<String>,
268	password_file: Option<String>,
269	origin_type: &str,
270	finalized: bool,
271) -> crate::error::Result<()> {
272	use qp_poseidon::PoseidonHasher;
273
274	log_print!("📝 Submitting System::remark Proposal to Referenda");
275	log_print!("   đŸ’Ŧ Message: {}", message.bright_cyan());
276	log_print!("   🔑 Submitted by: {}", from.bright_yellow());
277	log_print!("   đŸŽ¯ Origin type: {}", origin_type.bright_magenta());
278
279	// Load wallet keypair
280	let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
281
282	// Build System::remark call and encode it
283	let remark_bytes = message.as_bytes().to_vec();
284	let remark_payload = quantus_subxt::api::tx().system().remark(remark_bytes.clone());
285	let metadata = quantus_client.client().metadata();
286	let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&remark_payload, &metadata)
287		.map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?;
288
289	log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len());
290
291	// Compute preimage hash using Poseidon
292	let preimage_hash: sp_core::H256 =
293		<PoseidonHasher as sp_runtime::traits::Hash>::hash(&encoded_call);
294
295	log_print!("🔗 Preimage hash: {:?}", preimage_hash);
296
297	// Submit Preimage::note_preimage
298	type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes;
299	let bounded_bytes: PreimageBytes = encoded_call.clone();
300
301	log_print!("📝 Submitting preimage...");
302	let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes);
303	let preimage_tx_hash =
304		submit_transaction(quantus_client, &keypair, note_preimage_tx, None, finalized).await?;
305	log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash);
306
307	// Wait for preimage transaction confirmation
308	log_print!("âŗ Waiting for preimage transaction confirmation...");
309
310	// Build Referenda::submit call using Lookup preimage reference
311	type ProposalBounded =
312		quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
313			quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
314			quantus_subxt::api::runtime_types::qp_poseidon::PoseidonHasher,
315		>;
316
317	let preimage_hash_subxt: subxt::utils::H256 = preimage_hash;
318	let proposal: ProposalBounded =
319		ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 };
320
321	// Create origin based on origin_type parameter
322	let account_id_sp = keypair.to_account_id_32();
323	let account_id_subxt: subxt::ext::subxt_core::utils::AccountId32 =
324		subxt::ext::subxt_core::utils::AccountId32(*account_id_sp.as_ref());
325
326	let origin_caller = match origin_type.to_lowercase().as_str() {
327		"signed" => {
328			let raw_origin =
329				quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Signed(
330					account_id_subxt,
331				);
332			quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
333		},
334		"none" => {
335			let raw_origin =
336				quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::None;
337			quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
338		},
339		"root" => {
340			let raw_origin =
341				quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
342			quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
343		},
344		_ =>
345			return Err(QuantusError::Generic(format!(
346				"Invalid origin type: {}. Must be 'signed', 'none', or 'root'",
347				origin_type
348			))),
349	};
350
351	let enactment =
352		quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
353			10u32, // Execute 10 blocks after approval
354		);
355
356	log_print!("🔧 Creating Referenda::submit call...");
357	let submit_call =
358		quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment);
359
360	let tx_hash =
361		submit_transaction(quantus_client, &keypair, submit_call, None, finalized).await?;
362	log_print!(
363		"✅ {} Referendum proposal submitted! Hash: {:?}",
364		"SUCCESS".bright_green().bold(),
365		tx_hash
366	);
367
368	log_print!("💡 Use 'quantus referenda list' to see active proposals");
369	Ok(())
370}
371
372/// Submit a proposal using existing preimage hash
373async fn submit_proposal(
374	quantus_client: &crate::chain::client::QuantusClient,
375	preimage_hash: &str,
376	from: &str,
377	password: Option<String>,
378	password_file: Option<String>,
379	origin_type: &str,
380	finalized: bool,
381) -> crate::error::Result<()> {
382	log_print!("📝 Submitting Proposal to Referenda");
383	log_print!("   🔗 Preimage hash: {}", preimage_hash.bright_cyan());
384	log_print!("   🔑 Submitted by: {}", from.bright_yellow());
385	log_print!("   đŸŽ¯ Origin type: {}", origin_type.bright_magenta());
386
387	// Parse preimage hash
388	let hash_str = preimage_hash.trim_start_matches("0x");
389	let preimage_hash_parsed: sp_core::H256 = sp_core::H256::from_str(hash_str)
390		.map_err(|_| QuantusError::Generic("Invalid preimage hash format".to_string()))?;
391
392	// Load wallet keypair
393	let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
394
395	// Check if preimage exists and get its length
396	log_print!("🔍 Checking preimage status...");
397	let latest_block_hash = quantus_client.get_latest_block().await?;
398	let storage_at = quantus_client.client().storage().at(latest_block_hash);
399
400	let preimage_status = storage_at
401		.fetch(
402			&quantus_subxt::api::storage()
403				.preimage()
404				.request_status_for(preimage_hash_parsed),
405		)
406		.await
407		.map_err(|e| QuantusError::Generic(format!("Failed to fetch preimage status: {:?}", e)))?
408		.ok_or_else(|| QuantusError::Generic("Preimage not found on chain".to_string()))?;
409
410	let preimage_len = match preimage_status {
411		quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested {
412			ticket: _,
413			len,
414		} => len,
415		quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested {
416			maybe_ticket: _,
417			count: _,
418			maybe_len,
419		} => match maybe_len {
420			Some(len) => len,
421			None => return Err(QuantusError::Generic("Preimage length not available".to_string())),
422		},
423	};
424
425	log_print!("✅ Preimage found! Length: {} bytes", preimage_len);
426
427	// Build Referenda::submit call
428	type ProposalBounded =
429		quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
430			quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
431			quantus_subxt::api::runtime_types::qp_poseidon::PoseidonHasher,
432		>;
433
434	let preimage_hash_subxt: subxt::utils::H256 = preimage_hash_parsed;
435	let proposal: ProposalBounded =
436		ProposalBounded::Lookup { hash: preimage_hash_subxt, len: preimage_len };
437
438	// Create origin based on origin_type parameter
439	let account_id_sp = keypair.to_account_id_32();
440	let account_id_subxt: subxt::ext::subxt_core::utils::AccountId32 =
441		subxt::ext::subxt_core::utils::AccountId32(*account_id_sp.as_ref());
442
443	let origin_caller = match origin_type.to_lowercase().as_str() {
444		"signed" => {
445			let raw_origin =
446				quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Signed(
447					account_id_subxt,
448				);
449			quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
450		},
451		"none" => {
452			let raw_origin =
453				quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::None;
454			quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
455		},
456		"root" => {
457			let raw_origin =
458				quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
459			quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
460		},
461		_ =>
462			return Err(QuantusError::Generic(format!(
463				"Invalid origin type: {}. Must be 'signed', 'none', or 'root'",
464				origin_type
465			))),
466	};
467
468	let enactment =
469		quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
470			10u32,
471		);
472
473	log_print!("🔧 Creating Referenda::submit call...");
474	let submit_call =
475		quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment);
476
477	let tx_hash =
478		submit_transaction(quantus_client, &keypair, submit_call, None, finalized).await?;
479	log_print!(
480		"✅ {} Referendum proposal submitted! Hash: {:?}",
481		"SUCCESS".bright_green().bold(),
482		tx_hash
483	);
484
485	log_print!("💡 Use 'quantus referenda list' to see active proposals");
486	Ok(())
487}
488
489/// List recent Referenda proposals
490async fn list_proposals(
491	quantus_client: &crate::chain::client::QuantusClient,
492) -> crate::error::Result<()> {
493	log_print!("📜 Active Referenda Proposals");
494	log_print!("");
495
496	let addr = quantus_subxt::api::storage().referenda().referendum_count();
497
498	let latest_block_hash = quantus_client.get_latest_block().await?;
499	let storage_at = quantus_client.client().storage().at(latest_block_hash);
500
501	let count = storage_at.fetch(&addr).await?;
502
503	if let Some(total) = count {
504		log_print!("📊 Total referenda created: {}", total);
505		if total == 0 {
506			log_print!("📭 No active proposals found");
507			return Ok(());
508		}
509		log_print!("🔍 Fetching recent referenda...");
510		for i in (0..total).rev().take(10) {
511			get_proposal_status(quantus_client, i).await?;
512			log_print!("----------------------------------------");
513		}
514	} else {
515		log_print!("📭 No referenda found - Referenda may be empty");
516	}
517
518	Ok(())
519}
520
521/// Get details of a specific Referendum
522async fn get_proposal_details(
523	quantus_client: &crate::chain::client::QuantusClient,
524	index: u32,
525	decode: bool,
526) -> crate::error::Result<()> {
527	use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo;
528
529	log_print!("📄 Referendum #{} Details", index);
530	log_print!("");
531
532	let addr = quantus_subxt::api::storage().referenda().referendum_info_for(index);
533
534	let latest_block_hash = quantus_client.get_latest_block().await?;
535	let storage_at = quantus_client.client().storage().at(latest_block_hash);
536
537	let info = storage_at.fetch(&addr).await?;
538
539	if let Some(referendum_info) = info {
540		if decode {
541			// Try to decode the proposal
542			match &referendum_info {
543				ReferendumInfo::Ongoing(status) => {
544					log_print!("📊 {} Referendum #{}", "Ongoing".bright_green(), index);
545					log_print!("   đŸ›¤ī¸  Track: {}", status.track);
546					log_print!("   📅 Submitted: Block #{}", status.submitted);
547					log_print!(
548						"   đŸ—ŗī¸  Tally: Ayes: {}, Nays: {}, Support: {}",
549						status.tally.ayes,
550						status.tally.nays,
551						status.tally.support
552					);
553					log_print!("");
554
555					// Extract preimage hash and length from proposal
556					if let quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded::Lookup {
557						hash,
558						len,
559					} = &status.proposal
560					{
561						log_print!("📝 Proposal Details:");
562						log_print!("   🔗 Preimage Hash: {:?}", hash);
563						log_print!("   📏 Length: {} bytes", len);
564						log_print!("");
565
566						// Fetch and decode the preimage
567					match crate::cli::referenda_decode::decode_preimage(quantus_client, hash, *len).await {
568						Ok(decoded) => {
569							log_print!("✅ Decoded Proposal:");
570							log_print!("{}", decoded);
571						},
572						Err(e) => {
573							log_print!("âš ī¸  Could not decode proposal: {}", e);
574							log_print!("   Run 'quantus preimage get --hash {:?} --len {}' to see raw data", hash, len);
575						},
576					}
577					} else {
578						log_print!("âš ī¸  Proposal is inline (not a preimage lookup)");
579					}
580				},
581				ReferendumInfo::Approved(..) => {
582					log_print!("📊 {} Referendum #{}", "Approved".green(), index);
583					log_print!(
584						"   â„šī¸  Proposal details no longer available (referendum finalized)"
585					);
586				},
587				ReferendumInfo::Rejected(..) => {
588					log_print!("📊 {} Referendum #{}", "Rejected".red(), index);
589					log_print!(
590						"   â„šī¸  Proposal details no longer available (referendum finalized)"
591					);
592				},
593				ReferendumInfo::Cancelled(..) => {
594					log_print!("📊 {} Referendum #{}", "Cancelled".yellow(), index);
595					log_print!(
596						"   â„šī¸  Proposal details no longer available (referendum finalized)"
597					);
598				},
599				ReferendumInfo::TimedOut(..) => {
600					log_print!("📊 {} Referendum #{}", "TimedOut".dimmed(), index);
601					log_print!(
602						"   â„šī¸  Proposal details no longer available (referendum finalized)"
603					);
604				},
605				ReferendumInfo::Killed(..) => {
606					log_print!("📊 {} Referendum #{}", "Killed".red().bold(), index);
607					log_print!("   â„šī¸  Proposal details no longer available (referendum killed)");
608				},
609			}
610		} else {
611			// Raw output (original behavior)
612			log_print!("📋 Referendum Information (raw):");
613			log_print!("{:#?}", referendum_info);
614		}
615	} else {
616		log_print!("📭 Referendum #{} not found", index);
617	}
618	Ok(())
619}
620
621/// Get the status of a Referendum
622async fn get_proposal_status(
623	quantus_client: &crate::chain::client::QuantusClient,
624	index: u32,
625) -> crate::error::Result<()> {
626	use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo;
627
628	log_verbose!("📊 Fetching status for Referendum #{}...", index);
629
630	let addr = quantus_subxt::api::storage().referenda().referendum_info_for(index);
631
632	let latest_block_hash = quantus_client.get_latest_block().await?;
633	let storage_at = quantus_client.client().storage().at(latest_block_hash);
634
635	let info_res = storage_at.fetch(&addr).await;
636
637	match info_res {
638		Ok(Some(info)) => {
639			log_print!("📊 Status for Referendum #{}", index.to_string().bright_yellow());
640			match info {
641				ReferendumInfo::Ongoing(status) => {
642					log_print!("   - Status: {}", "Ongoing".bright_green());
643					log_print!("   - Track: {}", status.track);
644					log_print!("   - Submitted at: block {}", status.submitted);
645					log_print!(
646						"   - Tally: Ayes: {}, Nays: {}",
647						status.tally.ayes,
648						status.tally.nays
649					);
650					log_verbose!("   - Full status: {:#?}", status);
651				},
652				ReferendumInfo::Approved(submitted, ..) => {
653					log_print!("   - Status: {}", "Approved".green());
654					log_print!("   - Submitted at block: {}", submitted);
655				},
656				ReferendumInfo::Rejected(submitted, ..) => {
657					log_print!("   - Status: {}", "Rejected".red());
658					log_print!("   - Submitted at block: {}", submitted);
659				},
660				ReferendumInfo::Cancelled(submitted, ..) => {
661					log_print!("   - Status: {}", "Cancelled".yellow());
662					log_print!("   - Submitted at block: {}", submitted);
663				},
664				ReferendumInfo::TimedOut(submitted, ..) => {
665					log_print!("   - Status: {}", "TimedOut".dimmed());
666					log_print!("   - Submitted at block: {}", submitted);
667				},
668				ReferendumInfo::Killed(submitted) => {
669					log_print!("   - Status: {}", "Killed".red().bold());
670					log_print!("   - Killed at block: {}", submitted);
671				},
672			}
673		},
674		Ok(None) => log_print!("📭 Referendum #{} not found", index),
675		Err(e) => log_error!("❌ Failed to fetch referendum #{}: {:?}", index, e),
676	}
677
678	Ok(())
679}
680
681/// Place a decision deposit for a Referendum
682async fn place_decision_deposit(
683	quantus_client: &crate::chain::client::QuantusClient,
684	index: u32,
685	from: &str,
686	password: Option<String>,
687	password_file: Option<String>,
688	finalized: bool,
689) -> crate::error::Result<()> {
690	log_print!("📋 Placing decision deposit for Referendum #{}", index);
691	log_print!("   🔑 Placed by: {}", from.bright_yellow());
692
693	let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
694
695	let deposit_call = quantus_subxt::api::tx().referenda().place_decision_deposit(index);
696	let tx_hash =
697		submit_transaction(quantus_client, &keypair, deposit_call, None, finalized).await?;
698	log_success!("✅ Decision deposit placed! Hash: {:?}", tx_hash.to_string().bright_yellow());
699	Ok(())
700}
701
702/// Vote on a Referendum
703async fn vote_on_referendum(
704	quantus_client: &crate::chain::client::QuantusClient,
705	index: u32,
706	aye: bool,
707	conviction: u8,
708	amount: &str,
709	from: &str,
710	password: Option<String>,
711	password_file: Option<String>,
712	finalized: bool,
713) -> crate::error::Result<()> {
714	log_print!("đŸ—ŗī¸  Voting on Referendum #{}", index);
715	log_print!("   📊 Vote: {}", if aye { "AYE ✅".bright_green() } else { "NAY ❌".bright_red() });
716	log_print!("   💰 Amount: {}", amount.bright_cyan());
717	log_print!("   🔒 Conviction: {}", conviction);
718	log_print!("   🔑 Signed by: {}", from.bright_yellow());
719
720	let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
721
722	// Parse amount
723	let amount_value: u128 = (amount
724		.parse::<f64>()
725		.map_err(|_| QuantusError::Generic("Invalid amount format".to_string()))?
726		.max(0.0) *
727		1_000_000_000_000_000_000.0) as u128;
728
729	// Validate conviction
730	if conviction > 6 {
731		return Err(QuantusError::Generic("Invalid conviction (must be 0-6)".to_string()));
732	}
733
734	// Build vote
735	let vote =
736		quantus_subxt::api::runtime_types::pallet_conviction_voting::vote::AccountVote::Standard {
737			vote: quantus_subxt::api::runtime_types::pallet_conviction_voting::vote::Vote(
738				if aye { 128 } else { 0 } | conviction,
739			),
740			balance: amount_value,
741		};
742
743	let vote_call = quantus_subxt::api::tx().conviction_voting().vote(index, vote);
744	let tx_hash = submit_transaction(quantus_client, &keypair, vote_call, None, finalized).await?;
745
746	log_print!(
747		"✅ {} Vote transaction submitted! Hash: {:?}",
748		"SUCCESS".bright_green().bold(),
749		tx_hash
750	);
751
752	log_success!("🎉 {} Vote submitted!", "FINISHED".bright_green().bold());
753	Ok(())
754}
755
756/// Get Referenda configuration
757async fn get_config(
758	quantus_client: &crate::chain::client::QuantusClient,
759) -> crate::error::Result<()> {
760	log_print!("âš™ī¸  Referenda Configuration");
761	log_print!("");
762
763	let constants = quantus_client.client().constants();
764	let tracks_addr = quantus_subxt::api::constants().referenda().tracks();
765
766	match constants.at(&tracks_addr) {
767		Ok(tracks) => {
768			log_print!("{}", "📊 Track Configuration:".bold());
769			for (id, info) in tracks.iter() {
770				log_print!("   ------------------------------------");
771				log_print!(
772					"   â€ĸ {} #{}: {}",
773					"Track".bold(),
774					id,
775					info.name.to_string().bright_cyan()
776				);
777				log_print!("   â€ĸ Max Deciding: {}", info.max_deciding);
778				log_print!("   â€ĸ Decision Deposit: {}", info.decision_deposit);
779				log_print!("   â€ĸ Prepare Period: {} blocks", info.prepare_period);
780				log_print!("   â€ĸ Decision Period: {} blocks", info.decision_period);
781				log_print!("   â€ĸ Confirm Period: {} blocks", info.confirm_period);
782				log_print!("   â€ĸ Min Enactment Period: {} blocks", info.min_enactment_period);
783			}
784			log_print!("   ------------------------------------");
785		},
786		Err(e) => {
787			log_error!("❌ Failed to decode Tracks constant: {:?}", e);
788			log_print!("💡 It's possible the Tracks constant is not in the expected format.");
789		},
790	}
791
792	Ok(())
793}
794
795/// Refund submission deposit for a completed Referendum
796async fn refund_submission_deposit(
797	quantus_client: &crate::chain::client::QuantusClient,
798	index: u32,
799	from: &str,
800	password: Option<String>,
801	password_file: Option<String>,
802	finalized: bool,
803) -> crate::error::Result<()> {
804	log_print!("💰 Refunding submission deposit for Referendum #{}", index);
805	log_print!("   🔑 Refund to: {}", from.bright_yellow());
806
807	// Load wallet keypair
808	let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
809
810	// Create refund_submission_deposit call
811	let refund_call = quantus_subxt::api::tx().referenda().refund_submission_deposit(index);
812
813	let tx_hash =
814		submit_transaction(quantus_client, &keypair, refund_call, None, finalized).await?;
815	log_print!(
816		"✅ {} Refund transaction submitted! Hash: {:?}",
817		"SUCCESS".bright_green().bold(),
818		tx_hash
819	);
820
821	log_print!("💡 Check your balance to confirm the refund");
822	Ok(())
823}
824
825/// Refund decision deposit for a completed Referendum
826async fn refund_decision_deposit(
827	quantus_client: &crate::chain::client::QuantusClient,
828	index: u32,
829	from: &str,
830	password: Option<String>,
831	password_file: Option<String>,
832	finalized: bool,
833) -> crate::error::Result<()> {
834	log_print!("💰 Refunding decision deposit for Referendum #{}", index);
835	log_print!("   🔑 Refund to: {}", from.bright_yellow());
836
837	// Load wallet keypair
838	let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
839
840	// Create refund_decision_deposit call
841	let refund_call = quantus_subxt::api::tx().referenda().refund_decision_deposit(index);
842
843	let tx_hash =
844		submit_transaction(quantus_client, &keypair, refund_call, None, finalized).await?;
845	log_print!(
846		"✅ {} Refund transaction submitted! Hash: {:?}",
847		"SUCCESS".bright_green().bold(),
848		tx_hash
849	);
850
851	log_print!("💡 Check your balance to confirm the refund");
852	Ok(())
853}