From d7fc0457a5944f122201fd685b89fb69f54b150a Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 10 Apr 2026 15:40:07 +0800 Subject: [PATCH 1/6] better docs on multisig --- src/cli/multisig.rs | 63 +++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/cli/multisig.rs b/src/cli/multisig.rs index 0f117f1..673e035 100644 --- a/src/cli/multisig.rs +++ b/src/cli/multisig.rs @@ -10,6 +10,7 @@ use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec}; // Base unit (QUAN) decimals for amount conversions const QUAN_DECIMALS: u128 = 1_000_000_000_000; // 10^12 +const DEFAULT_TRANSFER_EXPIRY_BLOCKS: u32 = (2 * 60 * 60) / 10; // ~2h at 10s/block // ============================================================================ // PUBLIC LIBRARY API - Data Structures @@ -110,25 +111,26 @@ pub fn parse_amount(amount: &str) -> crate::error::Result { #[derive(Subcommand, Debug)] pub enum ProposeSubcommand { /// Propose a simple transfer (most common case) + #[command(arg_required_else_help = true)] Transfer { - /// Multisig account address (SS58 format) - #[arg(long)] + /// The multisig account address (SS58 format) + #[arg(long, value_name = "MULTISIG_ADDRESS")] address: String, - /// Recipient address (SS58 format) - #[arg(long)] + /// The recipient address (SS58 format or wallet name) + #[arg(long, value_name = "RECIPIENT")] to: String, - /// Amount to transfer (e.g., "10", "10.5", or raw "10000000000000") - #[arg(long)] + /// The amount to transfer (e.g. "10", "10.5", or raw base units) + #[arg(long, value_name = "AMOUNT")] amount: String, - /// Expiry block number (when this proposal expires) - #[arg(long)] - expiry: u32, + /// Expiry block number for this proposal. If omitted, defaults to head + ~2h + #[arg(long, value_name = "EXPIRY_BLOCK")] + expiry: Option, - /// Proposer wallet name (must be a signer) - #[arg(long)] + /// The proposer wallet name (must be a signer in this multisig) + #[arg(long, value_name = "PROPOSER_WALLET")] from: String, /// Password for the wallet @@ -215,9 +217,13 @@ pub enum ProposeSubcommand { #[derive(Subcommand, Debug)] pub enum MultisigCommands { /// Create a new multisig account + #[command( + arg_required_else_help = true, + after_help = "Examples:\n quantus multisig create --signers \"5F3sa2TJ...abc,5DAAnrj7...xyz,5HGjWAeF...123\" --threshold 2 --from alice\n quantus multisig create --signers \"alice,bob,charlie\" --threshold 2 --from alice" + )] Create { /// List of signer addresses (SS58 or wallet names), comma-separated - #[arg(long)] + #[arg(long, value_name = "SIGNERS_CSV")] signers: String, /// Number of approvals required to execute transactions @@ -1194,15 +1200,16 @@ async fn handle_create_multisig( if let Some(address) = actual_address { log_print!(""); - // Verify address matches prediction + // Use the on-chain event address as the source of truth. + log_success!("✅ Confirmed multisig address: {}", address.bright_cyan().bold()); if address == predicted_address { - log_success!("✅ Confirmed multisig address: {}", address.bright_cyan().bold()); log_print!(" {} Matches predicted address!", "✓".bright_green().bold()); } else { - log_error!("⚠️ Address mismatch!"); - log_print!(" Expected: {}", predicted_address.bright_yellow()); - log_print!(" Got: {}", address.bright_red()); - log_print!(" This should never happen with deterministic addresses!"); + log_verbose!( + "Predicted address differed from emitted address: predicted={}, emitted={}", + predicted_address, + address + ); } log_print!(""); @@ -1305,7 +1312,7 @@ async fn handle_propose_transfer( multisig_address: String, to: String, amount: String, - expiry: u32, + expiry: Option, from: String, password: Option, password_file: Option, @@ -1332,8 +1339,26 @@ async fn handle_propose_transfer( // Connect to chain and check if multisig has High Security enabled let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?; let latest_block_hash = quantus_client.get_latest_block().await?; + let latest_block = quantus_client.client().blocks().at(latest_block_hash).await?; + let current_block_number = latest_block.number(); let storage_at = quantus_client.client().storage().at(latest_block_hash); + let expiry = match expiry { + Some(expiry) => expiry, + None => { + let default_expiry = + current_block_number.saturating_add(DEFAULT_TRANSFER_EXPIRY_BLOCKS); + log_print!( + "⏱️ {} No --expiry provided; using block {} (head {} + {} blocks, ~2h)", + "DEFAULT".bright_blue().bold(), + default_expiry, + current_block_number, + DEFAULT_TRANSFER_EXPIRY_BLOCKS + ); + default_expiry + }, + }; + let hs_query = quantus_subxt::api::storage() .reversible_transfers() .high_security_accounts(multisig_account_id); From 152362b981e89d2624d76015f30cfee714dc9338 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 10 Apr 2026 15:49:37 +0800 Subject: [PATCH 2/6] fix predict multisig --- src/cli/multisig.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cli/multisig.rs b/src/cli/multisig.rs index 673e035..f07cef9 100644 --- a/src/cli/multisig.rs +++ b/src/cli/multisig.rs @@ -482,14 +482,11 @@ pub fn predict_multisig_address( data.extend_from_slice(&threshold.encode()); data.extend_from_slice(&nonce.encode()); - // Hash the data and map it deterministically into an AccountId - // CRITICAL: Use PoseidonHasher (same as runtime!) and TrailingZeroInput use codec::Decode; - use qp_poseidon::PoseidonHasher; use sp_core::crypto::AccountId32; - use sp_runtime::traits::{Hash as HashT, TrailingZeroInput}; + use sp_runtime::traits::{BlakeTwo256, Hash as HashT, TrailingZeroInput}; - let hash = PoseidonHasher::hash(&data); + let hash = BlakeTwo256::hash(&data); let account_id = AccountId32::decode(&mut TrailingZeroInput::new(hash.as_ref())) .expect("TrailingZeroInput provides sufficient bytes; qed"); From 67fa11399fdc1b92e61a01dfd77925cecef4beff Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 10 Apr 2026 15:53:41 +0800 Subject: [PATCH 3/6] preimage is also blake2 --- src/cli/block.rs | 1 - src/cli/preimage.rs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/cli/block.rs b/src/cli/block.rs index a5c6b1c..b123370 100644 --- a/src/cli/block.rs +++ b/src/cli/block.rs @@ -7,7 +7,6 @@ use crate::{ }; use clap::Subcommand; use colored::Colorize; -use qp_poseidon::PoseidonHasher; use sp_core::crypto::Ss58Codec; use std::str::FromStr; use subxt::events::EventDetails; diff --git a/src/cli/preimage.rs b/src/cli/preimage.rs index 1a5206e..3a32a4d 100644 --- a/src/cli/preimage.rs +++ b/src/cli/preimage.rs @@ -379,8 +379,6 @@ async fn create_preimage( password_file: Option, execution_mode: crate::cli::common::ExecutionMode, ) -> crate::error::Result<()> { - use qp_poseidon::PoseidonHasher; - log_print!("📦 Creating preimage from WASM file: {}", wasm_file.display()); log_print!(" 👤 From: {}", from_str.bright_yellow()); @@ -405,9 +403,8 @@ async fn create_preimage( log_verbose!("📝 Encoded call size: {} bytes", encoded_call.len()); - // Compute preimage hash using Poseidon (runtime uses PoseidonHasher) let preimage_hash: sp_core::H256 = - ::hash(&encoded_call); + ::hash(&encoded_call); log_print!("🔗 Preimage hash: {:?}", preimage_hash); From 8011dc88e9927a5563c1cd84e6cc82a6f545ca0c Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 10 Apr 2026 15:56:45 +0800 Subject: [PATCH 4/6] return poseidon hasher for extrinsics --- src/cli/block.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/block.rs b/src/cli/block.rs index b123370..a5c6b1c 100644 --- a/src/cli/block.rs +++ b/src/cli/block.rs @@ -7,6 +7,7 @@ use crate::{ }; use clap::Subcommand; use colored::Colorize; +use qp_poseidon::PoseidonHasher; use sp_core::crypto::Ss58Codec; use std::str::FromStr; use subxt::events::EventDetails; From c6af5c9ded90f656fad29e04eaba50b8c6819a2b Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 10 Apr 2026 16:10:44 +0800 Subject: [PATCH 5/6] add proposal id to output --- src/cli/multisig.rs | 54 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/cli/multisig.rs b/src/cli/multisig.rs index f07cef9..9bb506d 100644 --- a/src/cli/multisig.rs +++ b/src/cli/multisig.rs @@ -1425,6 +1425,43 @@ async fn handle_propose_transfer( } } +async fn fetch_proposal_id( + quantus_client: &crate::chain::client::QuantusClient, + multisig_ss58: &str, +) -> Option { + let latest_block_hash = quantus_client.get_latest_block().await.ok()?; + let events = quantus_client.client().events().at(latest_block_hash).await.ok()?; + for ev in events.find::() { + if let Ok(created) = ev { + let addr_bytes: &[u8; 32] = created.multisig_address.as_ref(); + let addr = SpAccountId32::from(*addr_bytes); + let addr_ss58 = + addr.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(189)); + if addr_ss58 == multisig_ss58 { + return Some(created.proposal_id); + } + } + } + None +} + +fn log_proposal_result(multisig_ss58: &str, proposal_id: Option) { + if let Some(id) = proposal_id { + log_print!(""); + log_success!("✅ Proposal #{} confirmed on-chain", id.to_string().bright_cyan().bold()); + log_print!(""); + log_print!("🚀 {} To approve this proposal, signers run:", "NEXT".bright_blue().bold()); + log_print!( + " quantus multisig approve --address {} --proposal-id {} --from ", + multisig_ss58.bright_cyan(), + id + ); + } else { + log_success!("✅ Proposal confirmed on-chain"); + log_print!(" Run `quantus multisig list-proposals --address {}` to find the proposal ID", multisig_ss58); + } +} + /// Propose a custom transaction async fn handle_propose( multisig_address: String, @@ -1564,7 +1601,8 @@ async fn handle_propose( ) .await?; - log_success!("✅ Proposal confirmed on-chain"); + let proposal_id = fetch_proposal_id(&quantus_client, &multisig_ss58).await; + log_proposal_result(&multisig_ss58, proposal_id); Ok(()) } @@ -1631,7 +1669,8 @@ async fn handle_propose_with_call_data( ) .await?; - log_success!("✅ Proposal confirmed on-chain"); + let proposal_id = fetch_proposal_id(quantus_client, &multisig_ss58).await; + log_proposal_result(&multisig_ss58, proposal_id); Ok(()) } @@ -3174,18 +3213,13 @@ async fn handle_high_security_set( ) .await?; - log_print!(""); - log_success!("✅ High-Security proposal confirmed on-chain!"); - log_print!(""); + let proposal_id = fetch_proposal_id(&quantus_client, &multisig_ss58).await; + log_proposal_result(&multisig_ss58, proposal_id); log_print!( "💡 {} Once this proposal reaches threshold, High-Security will be enabled", "NEXT STEPS".bright_blue().bold() ); - log_print!( - " - Other signers need to approve: quantus multisig approve --address {} --proposal-id --from ", - multisig_ss58.bright_cyan() - ); - log_print!(" - After threshold is reached, all transfers will be delayed and reversible"); + log_print!(" After threshold is reached, all transfers will be delayed and reversible"); log_print!(""); Ok(()) From ca5aa2b13e66a1201bf488ab426783ecafb53a5d Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 10 Apr 2026 16:47:01 +0800 Subject: [PATCH 6/6] preimage improvements, help improvements changed referenda voting to --vote from --aye --- src/cli/common.rs | 32 +++++++ src/cli/referenda.rs | 27 ++---- src/cli/tech_collective.rs | 35 ++++---- src/cli/tech_referenda.rs | 167 +++++++++++++++++-------------------- 4 files changed, 138 insertions(+), 123 deletions(-) diff --git a/src/cli/common.rs b/src/cli/common.rs index c82d6b1..84c2c3c 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -521,6 +521,38 @@ fn format_dispatch_error( } } +/// Submit a preimage, treating AlreadyNoted as success (idempotent). +/// Always waits for inclusion so subsequent txs from the same sender get a fresh nonce. +pub async fn submit_preimage( + quantus_client: &crate::chain::client::QuantusClient, + keypair: &crate::wallet::QuantumKeyPair, + encoded_call: Vec, + execution_mode: ExecutionMode, +) -> Result<()> { + type PreimageBytes = + crate::chain::quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; + let bounded_bytes: PreimageBytes = encoded_call; + + crate::log_print!("📝 Submitting preimage..."); + let note_preimage_tx = + crate::chain::quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); + let wait_mode = ExecutionMode { wait_for_transaction: true, ..execution_mode }; + + match submit_transaction(quantus_client, keypair, note_preimage_tx, None, wait_mode).await { + Ok(_) => { + crate::log_success!("Preimage submitted"); + }, + Err(e) if e.to_string().contains("AlreadyNoted") => { + crate::log_print!( + "✅ {} Preimage already exists on-chain, continuing", + "OK".bright_green().bold() + ); + }, + Err(e) => return Err(e), + } + Ok(()) +} + async fn check_execution_success( client: &OnlineClient, block_hash: &subxt::utils::H256, diff --git a/src/cli/referenda.rs b/src/cli/referenda.rs index 797252a..9320a2f 100644 --- a/src/cli/referenda.rs +++ b/src/cli/referenda.rs @@ -3,6 +3,7 @@ use crate::{ chain::quantus_subxt, cli::common::submit_transaction, error::QuantusError, log_error, log_print, log_success, log_verbose, }; +use crate::cli::tech_collective::VoteChoice; use clap::Subcommand; use colored::Colorize; use std::str::FromStr; @@ -102,9 +103,9 @@ pub enum ReferendaCommands { #[arg(short, long)] index: u32, - /// Vote aye (true) or nay (false) + /// Vote: "aye" or "nay" #[arg(long)] - aye: bool, + vote: VoteChoice, /// Conviction (0=None, 1=Locked1x, 2=Locked2x, up to 6=Locked6x) #[arg(long, default_value = "0")] @@ -216,7 +217,7 @@ pub async fn handle_referenda_command( .await, ReferendaCommands::Vote { index, - aye, + vote, conviction, amount, from, @@ -226,7 +227,7 @@ pub async fn handle_referenda_command( vote_on_referendum( &quantus_client, index, - aye, + matches!(vote, VoteChoice::Aye), conviction, &amount, &from, @@ -293,19 +294,9 @@ async fn submit_remark_proposal( log_print!("🔗 Preimage hash: {:?}", preimage_hash); - // Submit Preimage::note_preimage - type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; - let bounded_bytes: PreimageBytes = encoded_call.clone(); - - log_print!("📝 Submitting preimage..."); - let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); - let preimage_tx_hash = - submit_transaction(quantus_client, &keypair, note_preimage_tx, None, execution_mode) - .await?; - log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); - - // Wait for preimage transaction confirmation - log_print!("⏳ Waiting for preimage transaction confirmation..."); + let call_len = encoded_call.len() as u32; + crate::cli::common::submit_preimage(quantus_client, &keypair, encoded_call, execution_mode) + .await?; // Build Referenda::submit call using Lookup preimage reference type ProposalBounded = @@ -316,7 +307,7 @@ async fn submit_remark_proposal( let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; let proposal: ProposalBounded = - ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: call_len }; // Create origin based on origin_type parameter let account_id_sp = keypair.to_account_id_32(); diff --git a/src/cli/tech_collective.rs b/src/cli/tech_collective.rs index b9a9c41..6bf1e7f 100644 --- a/src/cli/tech_collective.rs +++ b/src/cli/tech_collective.rs @@ -5,10 +5,16 @@ use crate::{ error::QuantusError, log_print, log_success, log_verbose, }; -use clap::Subcommand; +use clap::{Subcommand, ValueEnum}; use colored::Colorize; use sp_core::crypto::{AccountId32, Ss58Codec}; +#[derive(Debug, Clone, ValueEnum)] +pub enum VoteChoice { + Aye, + Nay, +} + /// Tech Collective management commands #[derive(Subcommand, Debug)] pub enum TechCollectiveCommands { @@ -56,9 +62,9 @@ pub enum TechCollectiveCommands { #[arg(short, long)] referendum_index: u32, - /// Vote (true for aye, false for nay) + /// Vote: "aye" or "nay" #[arg(short, long)] - aye: bool, + vote: VoteChoice, /// Wallet name to sign with (must be a collective member) #[arg(short, long)] @@ -188,16 +194,14 @@ pub async fn vote_on_referendum( // Create the TechCollective::vote call let vote_call = quantus_subxt::api::tx().tech_collective().vote(referendum_index, aye); - let tx_hash = crate::cli::common::submit_transaction( - quantus_client, - from_keypair, - vote_call, - None, - execution_mode, - ) - .await?; + let wait_mode = + crate::cli::common::ExecutionMode { wait_for_transaction: true, ..execution_mode }; + + let tx_hash = + crate::cli::common::submit_transaction(quantus_client, from_keypair, vote_call, None, wait_mode) + .await?; - log_verbose!("📋 Vote transaction submitted: {:?}", tx_hash); + log_verbose!("📋 Vote transaction confirmed: {:?}", tx_hash); Ok(tx_hash) } @@ -348,7 +352,8 @@ pub async fn handle_tech_collective_command( ); }, - TechCollectiveCommands::Vote { referendum_index, aye, from, password, password_file } => { + TechCollectiveCommands::Vote { referendum_index, vote, from, password, password_file } => { + let aye = matches!(vote, VoteChoice::Aye); log_print!("🗳️ Voting on Tech Referendum #{} ", referendum_index); log_print!( " 📊 Vote: {}", @@ -356,10 +361,8 @@ pub async fn handle_tech_collective_command( ); log_print!(" 🔑 Signed by: {}", from.bright_yellow()); - // Load wallet let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?; - // Submit transaction let tx_hash = vote_on_referendum( &quantus_client, &keypair, @@ -370,7 +373,7 @@ pub async fn handle_tech_collective_command( .await?; log_print!( - "✅ {} Vote transaction submitted! Hash: {:?}", + "✅ {} Vote confirmed in block! Hash: {:?}", "SUCCESS".bright_green().bold(), tx_hash ); diff --git a/src/cli/tech_referenda.rs b/src/cli/tech_referenda.rs index ae8ef6a..030a832 100644 --- a/src/cli/tech_referenda.rs +++ b/src/cli/tech_referenda.rs @@ -7,144 +7,154 @@ use clap::Subcommand; use colored::Colorize; use std::{path::PathBuf, str::FromStr}; -/// Tech Referenda management commands +/// Tech Referenda: governance system for technical proposals (runtime upgrades, parameter changes). +/// +/// Proposals go through: Submit -> Decision Deposit -> Voting -> Enactment. +/// Only Tech Collective members can submit proposals. #[derive(Subcommand, Debug)] pub enum TechReferendaCommands { - /// Submit a runtime upgrade proposal to Tech Referenda (requires existing preimage) + /// Submit a runtime upgrade proposal using an existing on-chain preimage + #[command(arg_required_else_help = true)] Submit { - /// Preimage hash (must already exist on chain) - #[arg(long)] + /// Hash of the preimage already stored on-chain (hex, with or without 0x prefix) + #[arg(long, value_name = "HASH")] preimage_hash: String, - /// Wallet name to sign with (must be a Tech Collective member or root) - #[arg(short, long)] + /// Wallet name to sign with (must be a Tech Collective member) + #[arg(short, long, value_name = "WALLET")] from: String, - /// Password for the wallet #[arg(short, long)] password: Option, - /// Read password from file #[arg(long)] password_file: Option, }, - /// Submit a runtime upgrade proposal to Tech Referenda (creates preimage first) + /// Submit a runtime upgrade proposal (uploads WASM as preimage, then submits) + #[command(arg_required_else_help = true)] SubmitWithPreimage { - /// Path to the runtime WASM file - #[arg(short, long)] + /// Path to the compiled runtime WASM file to propose + #[arg(short, long, value_name = "PATH")] wasm_file: PathBuf, - /// Wallet name to sign with (must be a Tech Collective member or root) - #[arg(short, long)] + /// Wallet name to sign with (must be a Tech Collective member) + #[arg(short, long, value_name = "WALLET")] from: String, - /// Password for the wallet #[arg(short, long)] password: Option, - /// Read password from file #[arg(long)] password_file: Option, }, - /// Submit a proposal to set Treasury `treasury_portion` (Permill) via Tech Referenda (creates - /// preimage first) + /// Propose a new Treasury portion (% of block rewards sent to treasury) + /// + /// Creates the preimage and submits the referendum in one step. + #[command( + arg_required_else_help = true, + after_help = "Examples:\n quantus tech-referenda submit-treasury-portion --portion-permill 500000 --from alice # 50%\n quantus tech-referenda submit-treasury-portion --portion-permill 100000 --from alice # 10%" + )] SubmitTreasuryPortion { - /// New treasury portion in Permill (parts per million): 0..=1_000_000 - /// - /// Example: 500_000 = 50% - #[arg(long, value_parser = clap::value_parser!(u32).range(0..=1_000_000))] + /// New treasury portion in Permill (parts per million, 0-1000000). 500000 = 50% + #[arg(long, value_name = "PERMILL", value_parser = clap::value_parser!(u32).range(0..=1_000_000))] portion_permill: u32, - /// Wallet name to sign with (must be a Tech Collective member or root) - #[arg(short, long)] + /// Wallet name to sign with (must be a Tech Collective member) + #[arg(short, long, value_name = "WALLET")] from: String, - /// Password for the wallet #[arg(short, long)] password: Option, - /// Read password from file #[arg(long)] password_file: Option, }, - /// List all active Tech Referenda proposals + /// List all Tech Referenda proposals and their current status List, - /// Get details of a specific Tech Referendum + /// Show full details of a specific Tech Referendum (raw on-chain data) + #[command(arg_required_else_help = true)] Get { - /// Referendum index - #[arg(short, long)] + /// Referendum index (shown in `list` output) + #[arg(short, long, value_name = "REFERENDUM_INDEX")] index: u32, }, - /// Check the status of a Tech Referendum + /// Check the voting status and tally for a specific Tech Referendum + #[command(arg_required_else_help = true)] Status { - /// Referendum index - #[arg(short, long)] + /// Referendum index (shown in `list` output) + #[arg(short, long, value_name = "REFERENDUM_INDEX")] index: u32, }, - /// Place a decision deposit for a Tech Referendum + /// Place the decision deposit to move a referendum from Preparing to Deciding phase + /// + /// Required before voting can begin. The deposit is refundable after the referendum ends. + #[command( + arg_required_else_help = true, + after_help = "Example:\n quantus tech-referenda place-decision-deposit --index 0 --from alice" + )] PlaceDecisionDeposit { - /// Referendum index - #[arg(short, long)] + /// Referendum index to place the deposit for + #[arg(short, long, value_name = "REFERENDUM_INDEX")] index: u32, - /// Wallet name to sign with - #[arg(short, long)] + /// Wallet name to pay the deposit from (anyone can place it, not just the proposer) + #[arg(short, long, value_name = "WALLET")] from: String, - /// Password for the wallet #[arg(short, long)] password: Option, - /// Read password from file #[arg(long)] password_file: Option, }, - /// Refund submission deposit for a completed Tech Referendum + /// Refund the submission deposit after a Tech Referendum has completed + /// + /// Only callable after the referendum is no longer ongoing (approved/rejected/timed out). + #[command(arg_required_else_help = true)] RefundSubmissionDeposit { - /// Referendum index - #[arg(short, long)] + /// Referendum index to refund for + #[arg(short, long, value_name = "REFERENDUM_INDEX")] index: u32, - /// Wallet name that submitted the referendum - #[arg(short, long)] + /// Wallet name to sign the refund transaction + #[arg(short, long, value_name = "WALLET")] from: String, - /// Password for the wallet #[arg(short, long)] password: Option, - /// Read password from file #[arg(long)] password_file: Option, }, - /// Refund decision deposit for a completed Tech Referendum + /// Refund the decision deposit after a Tech Referendum has completed + /// + /// Only callable after the referendum is no longer ongoing (approved/rejected/timed out). + #[command(arg_required_else_help = true)] RefundDecisionDeposit { - /// Referendum index - #[arg(short, long)] + /// Referendum index to refund for + #[arg(short, long, value_name = "REFERENDUM_INDEX")] index: u32, - /// Wallet name that placed the decision deposit - #[arg(short, long)] + /// Wallet name to sign the refund transaction + #[arg(short, long, value_name = "WALLET")] from: String, - /// Password for the wallet #[arg(short, long)] password: Option, - /// Read password from file #[arg(long)] password_file: Option, }, - /// Get Tech Referenda configuration + /// Show Tech Referenda on-chain configuration (tracks, periods, deposits) Config, } @@ -369,19 +379,9 @@ async fn submit_runtime_upgrade_with_preimage( log_print!("🔗 Preimage hash: {:?}", preimage_hash); - // Submit Preimage::note_preimage with bounded bytes - type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; - let bounded_bytes: PreimageBytes = encoded_call.clone(); - - log_print!("📝 Submitting preimage..."); - let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); - let preimage_tx_hash = - submit_transaction(quantus_client, &keypair, note_preimage_tx, None, execution_mode) - .await?; - log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); - - // Wait for preimage transaction confirmation - log_print!("⏳ Waiting for preimage transaction confirmation..."); + let call_len = encoded_call.len() as u32; + crate::cli::common::submit_preimage(quantus_client, &keypair, encoded_call, execution_mode) + .await?; // Build TechReferenda::submit call using Lookup preimage reference type ProposalBounded = @@ -392,7 +392,7 @@ async fn submit_runtime_upgrade_with_preimage( let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; let proposal: ProposalBounded = - ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: call_len }; let raw_origin_root = quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; @@ -404,7 +404,7 @@ async fn submit_runtime_upgrade_with_preimage( 0u32, ); - log_print!("🔧 Creating TechReferenda::submit call..."); + log_print!("🔧 Submitting TechReferenda::submit..."); let submit_call = quantus_subxt::api::tx() .tech_referenda() @@ -412,9 +412,8 @@ async fn submit_runtime_upgrade_with_preimage( let tx_hash = submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; - log_print!( - "✅ {} Runtime upgrade proposal submitted! Hash: {:?}", - "SUCCESS".bright_green().bold(), + log_success!( + "Runtime upgrade proposal submitted! Hash: {:?}", tx_hash ); @@ -461,18 +460,9 @@ async fn submit_treasury_portion_with_preimage( let preimage_hash: sp_core::H256 = BlakeTwo256::hash(&encoded_call); log_print!("🔗 Preimage hash: {:?}", preimage_hash); - // Submit Preimage::note_preimage with bounded bytes - type PreimageBytes = quantus_subxt::api::preimage::calls::types::note_preimage::Bytes; - let bounded_bytes: PreimageBytes = encoded_call.clone(); - - log_print!("📝 Submitting preimage..."); - let note_preimage_tx = quantus_subxt::api::tx().preimage().note_preimage(bounded_bytes); - let preimage_tx_hash = - submit_transaction(quantus_client, &keypair, note_preimage_tx, None, execution_mode) - .await?; - log_print!("✅ Preimage transaction submitted: {:?}", preimage_tx_hash); - - log_print!("⏳ Waiting for preimage transaction confirmation..."); + let call_len = encoded_call.len() as u32; + crate::cli::common::submit_preimage(quantus_client, &keypair, encoded_call, execution_mode) + .await?; // Build TechReferenda::submit call using Lookup preimage reference type ProposalBounded = @@ -483,7 +473,7 @@ async fn submit_treasury_portion_with_preimage( let preimage_hash_subxt: subxt::utils::H256 = preimage_hash; let proposal: ProposalBounded = - ProposalBounded::Lookup { hash: preimage_hash_subxt, len: encoded_call.len() as u32 }; + ProposalBounded::Lookup { hash: preimage_hash_subxt, len: call_len }; let raw_origin_root = quantus_subxt::api::runtime_types::frame_support::dispatch::RawOrigin::Root; @@ -495,7 +485,7 @@ async fn submit_treasury_portion_with_preimage( 0u32, ); - log_print!("🔧 Creating TechReferenda::submit call..."); + log_print!("🔧 Submitting TechReferenda::submit..."); let submit_call = quantus_subxt::api::tx() .tech_referenda() @@ -503,9 +493,8 @@ async fn submit_treasury_portion_with_preimage( let tx_hash = submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?; - log_print!( - "✅ {} Treasury portion proposal submitted! Hash: {:?}", - "SUCCESS".bright_green().bold(), + log_success!( + "Treasury portion proposal submitted! Hash: {:?}", tx_hash );