use crate::{
chain::quantus_subxt,
cli::{common::submit_transaction, tech_collective::VoteChoice},
error::QuantusError,
log_error, log_print, log_success, log_verbose,
};
use clap::Subcommand;
use colored::Colorize;
use std::str::FromStr;
#[derive(Subcommand, Debug)]
pub enum ReferendaCommands {
SubmitRemark {
#[arg(long)]
message: String,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
#[arg(long, default_value = "signed")]
origin: String,
},
Submit {
#[arg(long)]
preimage_hash: String,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
#[arg(long, default_value = "signed")]
origin: String,
},
List,
Get {
#[arg(short, long)]
index: u32,
#[arg(long)]
decode: bool,
},
Status {
#[arg(short, long)]
index: u32,
},
PlaceDecisionDeposit {
#[arg(short, long)]
index: u32,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
Vote {
#[arg(short, long)]
index: u32,
#[arg(long)]
vote: VoteChoice,
#[arg(long, default_value = "0")]
conviction: u8,
#[arg(long)]
amount: String,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
RefundSubmissionDeposit {
#[arg(short, long)]
index: u32,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
RefundDecisionDeposit {
#[arg(short, long)]
index: u32,
#[arg(short, long)]
from: String,
#[arg(short, long)]
password: Option<String>,
#[arg(long)]
password_file: Option<String>,
},
Config,
}
pub async fn handle_referenda_command(
command: ReferendaCommands,
node_url: &str,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
match command {
ReferendaCommands::SubmitRemark { message, from, password, password_file, origin } =>
submit_remark_proposal(
&quantus_client,
&message,
&from,
password,
password_file,
&origin,
execution_mode,
)
.await,
ReferendaCommands::Submit { preimage_hash, from, password, password_file, origin } =>
submit_proposal(
&quantus_client,
&preimage_hash,
&from,
password,
password_file,
&origin,
execution_mode,
)
.await,
ReferendaCommands::List => list_proposals(&quantus_client).await,
ReferendaCommands::Get { index, decode } =>
get_proposal_details(&quantus_client, index, decode).await,
ReferendaCommands::Status { index } => get_proposal_status(&quantus_client, index).await,
ReferendaCommands::PlaceDecisionDeposit { index, from, password, password_file } =>
place_decision_deposit(
&quantus_client,
index,
&from,
password,
password_file,
execution_mode,
)
.await,
ReferendaCommands::Vote {
index,
vote,
conviction,
amount,
from,
password,
password_file,
} =>
vote_on_referendum(
&quantus_client,
index,
matches!(vote, VoteChoice::Aye),
conviction,
&amount,
&from,
password,
password_file,
execution_mode,
)
.await,
ReferendaCommands::RefundSubmissionDeposit { index, from, password, password_file } =>
refund_submission_deposit(
&quantus_client,
index,
&from,
password,
password_file,
execution_mode,
)
.await,
ReferendaCommands::RefundDecisionDeposit { index, from, password, password_file } =>
refund_decision_deposit(
&quantus_client,
index,
&from,
password,
password_file,
execution_mode,
)
.await,
ReferendaCommands::Config => get_config(&quantus_client).await,
}
}
async fn submit_remark_proposal(
quantus_client: &crate::chain::client::QuantusClient,
message: &str,
from: &str,
password: Option<String>,
password_file: Option<String>,
origin_type: &str,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
use sp_runtime::traits::{BlakeTwo256, Hash};
log_print!("📝 Submitting System::remark Proposal to Referenda");
log_print!(" 💬 Message: {}", message.bright_cyan());
log_print!(" 🔑 Submitted by: {}", from.bright_yellow());
log_print!(" 🎯 Origin type: {}", origin_type.bright_magenta());
let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
let remark_bytes = message.as_bytes().to_vec();
let remark_payload = quantus_subxt::api::tx().system().remark(remark_bytes.clone());
let metadata = quantus_client.client().metadata();
let encoded_call = <_ as subxt::tx::Payload>::encode_call_data(&remark_payload, &metadata)
.map_err(|e| QuantusError::Generic(format!("Failed to encode call data: {:?}", e)))?;
log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len());
let preimage_hash: sp_core::H256 = BlakeTwo256::hash(&encoded_call);
log_print!("🔗 Preimage hash: {:?}", preimage_hash);
let call_len = encoded_call.len() as u32;
crate::cli::common::submit_preimage(quantus_client, &keypair, encoded_call, execution_mode)
.await?;
type ProposalBounded =
quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256,
>;
let preimage_hash_subxt: subxt::utils::H256 = preimage_hash;
let proposal: ProposalBounded =
ProposalBounded::Lookup { hash: preimage_hash_subxt, len: call_len };
let account_id_sp = keypair.to_account_id_32();
let account_id_subxt: subxt::ext::subxt_core::utils::AccountId32 =
subxt::ext::subxt_core::utils::AccountId32(*account_id_sp.as_ref());
let origin_caller = match origin_type.to_lowercase().as_str() {
"signed" => {
let raw_origin =
quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Signed(
account_id_subxt,
);
quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
},
"root" => {
let raw_origin =
quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
},
"none" =>
return Err(QuantusError::Generic(
"Invalid origin type: none. Use 'signed' or 'root'.".to_string(),
)),
_ =>
return Err(QuantusError::Generic(format!(
"Invalid origin type: {}. Must be 'signed' or 'root'",
origin_type
))),
};
let enactment =
quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
10u32, );
log_print!("🔧 Creating Referenda::submit call...");
let submit_call =
quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment);
let tx_hash =
submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?;
log_print!(
"✅ {} Referendum proposal submitted! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
log_print!("💡 Use 'quantus referenda list' to see active proposals");
Ok(())
}
async fn submit_proposal(
quantus_client: &crate::chain::client::QuantusClient,
preimage_hash: &str,
from: &str,
password: Option<String>,
password_file: Option<String>,
origin_type: &str,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
log_print!("📝 Submitting Proposal to Referenda");
log_print!(" 🔗 Preimage hash: {}", preimage_hash.bright_cyan());
log_print!(" 🔑 Submitted by: {}", from.bright_yellow());
log_print!(" 🎯 Origin type: {}", origin_type.bright_magenta());
let hash_str = preimage_hash.trim_start_matches("0x");
let preimage_hash_parsed: sp_core::H256 = sp_core::H256::from_str(hash_str)
.map_err(|_| QuantusError::Generic("Invalid preimage hash format".to_string()))?;
let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
log_print!("🔍 Checking preimage status...");
let latest_block_hash = quantus_client.get_latest_block().await?;
let storage_at = quantus_client.client().storage().at(latest_block_hash);
let preimage_status = storage_at
.fetch(
&quantus_subxt::api::storage()
.preimage()
.request_status_for(preimage_hash_parsed),
)
.await
.map_err(|e| QuantusError::Generic(format!("Failed to fetch preimage status: {:?}", e)))?
.ok_or_else(|| QuantusError::Generic("Preimage not found on chain".to_string()))?;
let preimage_len = match preimage_status {
quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Unrequested {
ticket: _,
len,
} => len,
quantus_subxt::api::runtime_types::pallet_preimage::RequestStatus::Requested {
maybe_ticket: _,
count: _,
maybe_len,
} => match maybe_len {
Some(len) => len,
None => return Err(QuantusError::Generic("Preimage length not available".to_string())),
},
};
log_print!("✅ Preimage found! Length: {} bytes", preimage_len);
type ProposalBounded =
quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded<
quantus_subxt::api::runtime_types::quantus_runtime::RuntimeCall,
quantus_subxt::api::runtime_types::sp_runtime::traits::BlakeTwo256,
>;
let preimage_hash_subxt: subxt::utils::H256 = preimage_hash_parsed;
let proposal: ProposalBounded =
ProposalBounded::Lookup { hash: preimage_hash_subxt, len: preimage_len };
let account_id_sp = keypair.to_account_id_32();
let account_id_subxt: subxt::ext::subxt_core::utils::AccountId32 =
subxt::ext::subxt_core::utils::AccountId32(*account_id_sp.as_ref());
let origin_caller = match origin_type.to_lowercase().as_str() {
"signed" => {
let raw_origin =
quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Signed(
account_id_subxt,
);
quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
},
"root" => {
let raw_origin =
quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root;
quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::system(raw_origin)
},
"none" =>
return Err(QuantusError::Generic(
"Invalid origin type: none. Use 'signed' or 'root'.".to_string(),
)),
_ =>
return Err(QuantusError::Generic(format!(
"Invalid origin type: {}. Must be 'signed' or 'root'",
origin_type
))),
};
let enactment =
quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
10u32,
);
log_print!("🔧 Creating Referenda::submit call...");
let submit_call =
quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment);
let tx_hash =
submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?;
log_print!(
"✅ {} Referendum proposal submitted! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
log_print!("💡 Use 'quantus referenda list' to see active proposals");
Ok(())
}
async fn list_proposals(
quantus_client: &crate::chain::client::QuantusClient,
) -> crate::error::Result<()> {
log_print!("📜 Active Referenda Proposals");
log_print!("");
let addr = quantus_subxt::api::storage().referenda().referendum_count();
let latest_block_hash = quantus_client.get_latest_block().await?;
let storage_at = quantus_client.client().storage().at(latest_block_hash);
let count = storage_at.fetch(&addr).await?;
if let Some(total) = count {
log_print!("📊 Total referenda created: {}", total);
if total == 0 {
log_print!("📭 No active proposals found");
return Ok(());
}
log_print!("🔍 Fetching recent referenda...");
for i in (0..total).rev().take(10) {
get_proposal_status(quantus_client, i).await?;
log_print!("----------------------------------------");
}
} else {
log_print!("📭 No referenda found - Referenda may be empty");
}
Ok(())
}
async fn get_proposal_details(
quantus_client: &crate::chain::client::QuantusClient,
index: u32,
decode: bool,
) -> crate::error::Result<()> {
use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo;
log_print!("📄 Referendum #{} Details", index);
log_print!("");
let addr = quantus_subxt::api::storage().referenda().referendum_info_for(index);
let latest_block_hash = quantus_client.get_latest_block().await?;
let storage_at = quantus_client.client().storage().at(latest_block_hash);
let info = storage_at.fetch(&addr).await?;
if let Some(referendum_info) = info {
if decode {
match &referendum_info {
ReferendumInfo::Ongoing(status) => {
log_print!("📊 {} Referendum #{}", "Ongoing".bright_green(), index);
log_print!(" 🛤️ Track: {}", status.track);
log_print!(" 📅 Submitted: Block #{}", status.submitted);
log_print!(
" 🗳️ Tally: Ayes: {}, Nays: {}, Support: {}",
status.tally.ayes,
status.tally.nays,
status.tally.support
);
log_print!("");
if let quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded::Lookup {
hash,
len,
} = &status.proposal
{
log_print!("📝 Proposal Details:");
log_print!(" 🔗 Preimage Hash: {:?}", hash);
log_print!(" 📏 Length: {} bytes", len);
log_print!("");
match crate::cli::referenda_decode::decode_preimage(quantus_client, hash, *len).await {
Ok(decoded) => {
log_print!("✅ Decoded Proposal:");
log_print!("{}", decoded);
},
Err(e) => {
log_print!("⚠️ Could not decode proposal: {}", e);
log_print!(" Run 'quantus preimage get --hash {:?} --len {}' to see raw data", hash, len);
},
}
} else {
log_print!("⚠️ Proposal is inline (not a preimage lookup)");
}
},
ReferendumInfo::Approved(..) => {
log_print!("📊 {} Referendum #{}", "Approved".green(), index);
log_print!(
" ℹ️ Proposal details no longer available (referendum finalized)"
);
},
ReferendumInfo::Rejected(..) => {
log_print!("📊 {} Referendum #{}", "Rejected".red(), index);
log_print!(
" ℹ️ Proposal details no longer available (referendum finalized)"
);
},
ReferendumInfo::Cancelled(..) => {
log_print!("📊 {} Referendum #{}", "Cancelled".yellow(), index);
log_print!(
" ℹ️ Proposal details no longer available (referendum finalized)"
);
},
ReferendumInfo::TimedOut(..) => {
log_print!("📊 {} Referendum #{}", "TimedOut".dimmed(), index);
log_print!(
" ℹ️ Proposal details no longer available (referendum finalized)"
);
},
ReferendumInfo::Killed(..) => {
log_print!("📊 {} Referendum #{}", "Killed".red().bold(), index);
log_print!(" ℹ️ Proposal details no longer available (referendum killed)");
},
}
} else {
log_print!("📋 Referendum Information (raw):");
log_print!("{:#?}", referendum_info);
}
} else {
log_print!("📭 Referendum #{} not found", index);
}
Ok(())
}
async fn get_proposal_status(
quantus_client: &crate::chain::client::QuantusClient,
index: u32,
) -> crate::error::Result<()> {
use quantus_subxt::api::runtime_types::pallet_referenda::types::ReferendumInfo;
log_verbose!("📊 Fetching status for Referendum #{}...", index);
let addr = quantus_subxt::api::storage().referenda().referendum_info_for(index);
let latest_block_hash = quantus_client.get_latest_block().await?;
let storage_at = quantus_client.client().storage().at(latest_block_hash);
let info_res = storage_at.fetch(&addr).await;
match info_res {
Ok(Some(info)) => {
log_print!("📊 Status for Referendum #{}", index.to_string().bright_yellow());
match info {
ReferendumInfo::Ongoing(status) => {
log_print!(" - Status: {}", "Ongoing".bright_green());
log_print!(" - Track: {}", status.track);
log_print!(" - Submitted at: block {}", status.submitted);
log_print!(
" - Tally: Ayes: {}, Nays: {}",
status.tally.ayes,
status.tally.nays
);
log_verbose!(" - Full status: {:#?}", status);
},
ReferendumInfo::Approved(submitted, ..) => {
log_print!(" - Status: {}", "Approved".green());
log_print!(" - Submitted at block: {}", submitted);
},
ReferendumInfo::Rejected(submitted, ..) => {
log_print!(" - Status: {}", "Rejected".red());
log_print!(" - Submitted at block: {}", submitted);
},
ReferendumInfo::Cancelled(submitted, ..) => {
log_print!(" - Status: {}", "Cancelled".yellow());
log_print!(" - Submitted at block: {}", submitted);
},
ReferendumInfo::TimedOut(submitted, ..) => {
log_print!(" - Status: {}", "TimedOut".dimmed());
log_print!(" - Submitted at block: {}", submitted);
},
ReferendumInfo::Killed(submitted) => {
log_print!(" - Status: {}", "Killed".red().bold());
log_print!(" - Killed at block: {}", submitted);
},
}
},
Ok(None) => log_print!("📭 Referendum #{} not found", index),
Err(e) => log_error!("❌ Failed to fetch referendum #{}: {:?}", index, e),
}
Ok(())
}
async fn place_decision_deposit(
quantus_client: &crate::chain::client::QuantusClient,
index: u32,
from: &str,
password: Option<String>,
password_file: Option<String>,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
log_print!("📋 Placing decision deposit for Referendum #{}", index);
log_print!(" 🔑 Placed by: {}", from.bright_yellow());
let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
let deposit_call = quantus_subxt::api::tx().referenda().place_decision_deposit(index);
let tx_hash =
submit_transaction(quantus_client, &keypair, deposit_call, None, execution_mode).await?;
log_success!("✅ Decision deposit placed! Hash: {:?}", tx_hash.to_string().bright_yellow());
Ok(())
}
async fn vote_on_referendum(
quantus_client: &crate::chain::client::QuantusClient,
index: u32,
aye: bool,
conviction: u8,
amount: &str,
from: &str,
password: Option<String>,
password_file: Option<String>,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
log_print!("🗳️ Voting on Referendum #{}", index);
log_print!(" 📊 Vote: {}", if aye { "AYE ✅".bright_green() } else { "NAY ❌".bright_red() });
log_print!(" 💰 Amount: {}", amount.bright_cyan());
log_print!(" 🔒 Conviction: {}", conviction);
log_print!(" 🔑 Signed by: {}", from.bright_yellow());
let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
let amount_value: u128 = crate::cli::send::parse_amount(quantus_client, amount).await?;
if conviction > 6 {
return Err(QuantusError::Generic("Invalid conviction (must be 0-6)".to_string()));
}
let vote =
quantus_subxt::api::runtime_types::pallet_conviction_voting::vote::AccountVote::Standard {
vote: quantus_subxt::api::runtime_types::pallet_conviction_voting::vote::Vote(
if aye { 128 } else { 0 } | conviction,
),
balance: amount_value,
};
let vote_call = quantus_subxt::api::tx().conviction_voting().vote(index, vote);
let tx_hash =
submit_transaction(quantus_client, &keypair, vote_call, None, execution_mode).await?;
log_print!(
"✅ {} Vote transaction submitted! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
log_success!("🎉 {} Vote submitted!", "FINISHED".bright_green().bold());
Ok(())
}
async fn get_config(
quantus_client: &crate::chain::client::QuantusClient,
) -> crate::error::Result<()> {
log_print!("⚙️ Referenda Configuration");
log_print!("");
let constants = quantus_client.client().constants();
let tracks_addr = quantus_subxt::api::constants().referenda().tracks();
match constants.at(&tracks_addr) {
Ok(tracks) => {
log_print!("{}", "📊 Track Configuration:".bold());
for (id, info) in tracks.iter() {
log_print!(" ------------------------------------");
log_print!(
" • {} #{}: {}",
"Track".bold(),
id,
info.name.to_string().bright_cyan()
);
log_print!(" • Max Deciding: {}", info.max_deciding);
log_print!(" • Decision Deposit: {}", info.decision_deposit);
log_print!(" • Prepare Period: {} blocks", info.prepare_period);
log_print!(" • Decision Period: {} blocks", info.decision_period);
log_print!(" • Confirm Period: {} blocks", info.confirm_period);
log_print!(" • Min Enactment Period: {} blocks", info.min_enactment_period);
}
log_print!(" ------------------------------------");
},
Err(e) => {
log_error!("❌ Failed to decode Tracks constant: {:?}", e);
log_print!("💡 It's possible the Tracks constant is not in the expected format.");
},
}
Ok(())
}
async fn refund_submission_deposit(
quantus_client: &crate::chain::client::QuantusClient,
index: u32,
from: &str,
password: Option<String>,
password_file: Option<String>,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
log_print!("💰 Refunding submission deposit for Referendum #{}", index);
log_print!(" 🔑 Refund to: {}", from.bright_yellow());
let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
let refund_call = quantus_subxt::api::tx().referenda().refund_submission_deposit(index);
let tx_hash =
submit_transaction(quantus_client, &keypair, refund_call, None, execution_mode).await?;
log_print!(
"✅ {} Refund transaction submitted! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
log_print!("💡 Check your balance to confirm the refund");
Ok(())
}
async fn refund_decision_deposit(
quantus_client: &crate::chain::client::QuantusClient,
index: u32,
from: &str,
password: Option<String>,
password_file: Option<String>,
execution_mode: crate::cli::common::ExecutionMode,
) -> crate::error::Result<()> {
log_print!("💰 Refunding decision deposit for Referendum #{}", index);
log_print!(" 🔑 Refund to: {}", from.bright_yellow());
let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
let refund_call = quantus_subxt::api::tx().referenda().refund_decision_deposit(index);
let tx_hash =
submit_transaction(quantus_client, &keypair, refund_call, None, execution_mode).await?;
log_print!(
"✅ {} Refund transaction submitted! Hash: {:?}",
"SUCCESS".bright_green().bold(),
tx_hash
);
log_print!("💡 Check your balance to confirm the refund");
Ok(())
}