1use crate::{chain::quantus_subxt, cli::common::submit_transaction, log_print, log_success};
3use clap::Subcommand;
4use colored::Colorize;
5use frame_support::sp_runtime::traits::AccountIdConversion;
6
7#[derive(Subcommand, Debug)]
9pub enum TreasuryCommands {
10 Balance,
12
13 Config,
15
16 Info,
18
19 SubmitSpend {
22 #[arg(long)]
24 beneficiary: String,
25
26 #[arg(long)]
28 amount: String,
29
30 #[arg(long)]
36 track: String,
37
38 #[arg(long)]
40 from: String,
41
42 #[arg(long)]
44 password: Option<String>,
45
46 #[arg(long)]
48 password_file: Option<String>,
49 },
50
51 Payout {
53 #[arg(long)]
55 index: u32,
56
57 #[arg(long)]
59 from: String,
60
61 #[arg(long)]
63 password: Option<String>,
64
65 #[arg(long)]
67 password_file: Option<String>,
68 },
69
70 CheckStatus {
72 #[arg(long)]
74 index: u32,
75
76 #[arg(long)]
78 from: String,
79
80 #[arg(long)]
82 password: Option<String>,
83
84 #[arg(long)]
86 password_file: Option<String>,
87 },
88
89 ListSpends,
91
92 SpendSudo {
94 #[arg(long)]
96 beneficiary: String,
97
98 #[arg(long)]
100 amount: String,
101
102 #[arg(long)]
104 from: String,
105
106 #[arg(long)]
108 password: Option<String>,
109
110 #[arg(long)]
112 password_file: Option<String>,
113 },
114}
115
116pub async fn handle_treasury_command(
118 command: TreasuryCommands,
119 node_url: &str,
120 finalized: bool,
121) -> crate::error::Result<()> {
122 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
123
124 match command {
125 TreasuryCommands::Balance => get_treasury_balance(&quantus_client).await,
126 TreasuryCommands::Config => get_config(&quantus_client).await,
127 TreasuryCommands::Info => show_treasury_info().await,
128 TreasuryCommands::SubmitSpend {
129 beneficiary,
130 amount,
131 track,
132 from,
133 password,
134 password_file,
135 } =>
136 submit_spend_referendum(
137 &quantus_client,
138 &beneficiary,
139 &amount,
140 &track,
141 &from,
142 password,
143 password_file,
144 finalized,
145 )
146 .await,
147 TreasuryCommands::Payout { index, from, password, password_file } =>
148 payout_spend(&quantus_client, index, &from, password, password_file, finalized).await,
149 TreasuryCommands::CheckStatus { index, from, password, password_file } =>
150 check_spend_status(&quantus_client, index, &from, password, password_file, finalized)
151 .await,
152 TreasuryCommands::ListSpends => list_spends(&quantus_client).await,
153 TreasuryCommands::SpendSudo { beneficiary, amount, from, password, password_file } =>
154 spend_sudo(
155 &quantus_client,
156 &beneficiary,
157 &amount,
158 &from,
159 password,
160 password_file,
161 finalized,
162 )
163 .await,
164 }
165}
166
167async fn get_treasury_balance(
169 quantus_client: &crate::chain::client::QuantusClient,
170) -> crate::error::Result<()> {
171 log_print!("💰 Treasury Balance");
172 log_print!("");
173
174 let treasury_pallet_id = frame_support::PalletId(*b"py/trsry");
176 let treasury_account_raw: sp_runtime::AccountId32 =
177 treasury_pallet_id.into_account_truncating();
178
179 let treasury_account = subxt::utils::AccountId32(*treasury_account_raw.as_ref());
181
182 let addr = quantus_subxt::api::storage().system().account(treasury_account.clone());
184
185 let latest_block_hash = quantus_client.get_latest_block().await?;
186 let storage_at = quantus_client.client().storage().at(latest_block_hash);
187
188 let account_info = storage_at.fetch(&addr).await?.ok_or_else(|| {
189 crate::error::QuantusError::Generic("Treasury account not found".to_string())
190 })?;
191
192 let free_balance = account_info.data.free;
193 let reserved_balance = account_info.data.reserved;
194
195 let formatted_free_balance =
196 crate::cli::send::format_balance_with_symbol(quantus_client, free_balance).await?;
197 let formatted_reserved_balance =
198 crate::cli::send::format_balance_with_symbol(quantus_client, reserved_balance).await?;
199
200 log_print!("💰 Free Balance: {}", formatted_free_balance);
201 log_print!("💰 Reserved: {}", formatted_reserved_balance);
202
203 use crate::cli::address_format::QuantusSS58;
205 let treasury_address = treasury_account_raw.to_quantus_ss58();
206 log_print!("📍 Treasury Account: {}", treasury_address.bright_yellow());
207
208 Ok(())
209}
210
211async fn get_config(
213 quantus_client: &crate::chain::client::QuantusClient,
214) -> crate::error::Result<()> {
215 log_print!("⚙️ Treasury Configuration");
216 log_print!("");
217
218 let constants = quantus_client.client().constants();
219
220 if let Ok(spend_period) =
222 constants.at(&quantus_subxt::api::constants().treasury_pallet().spend_period())
223 {
224 log_print!("⏰ Spend Period: {} blocks", spend_period.to_string().bright_cyan());
225 let hours = spend_period as f64 * 3.0 / 3600.0; log_print!(" (~{:.1} hours)", hours);
227 }
228
229 if let Ok(burn) = constants.at(&quantus_subxt::api::constants().treasury_pallet().burn()) {
231 log_print!("🔥 Burn: {:?}", burn);
232 }
233
234 if let Ok(max_approvals) =
236 constants.at(&quantus_subxt::api::constants().treasury_pallet().max_approvals())
237 {
238 log_print!("📊 Max Approvals: {}", max_approvals.to_string().bright_yellow());
239 }
240
241 if let Ok(payout_period) =
243 constants.at(&quantus_subxt::api::constants().treasury_pallet().payout_period())
244 {
245 log_print!("💸 Payout Period: {} blocks", payout_period.to_string().bright_green());
246 let days = payout_period as f64 * 3.0 / 86400.0; log_print!(" (~{:.1} days)", days);
248 }
249
250 Ok(())
251}
252
253async fn show_treasury_info() -> crate::error::Result<()> {
255 log_print!("💰 Treasury Information");
256 log_print!("");
257 log_print!("The Treasury is a pot of funds collected through:");
258 log_print!(" • Transaction fees");
259 log_print!(" • Slashing");
260 log_print!(" • Other network mechanisms");
261 log_print!("");
262 log_print!("📋 {} To spend from Treasury:", "HOW TO USE".bright_cyan().bold());
263 log_print!("");
264 log_print!(
265 "1. {} Create a spending proposal using Referenda:",
266 "Treasury Tracks".bright_yellow().bold()
267 );
268 log_print!(" • Track 2: Treasury Small Spender (< certain amount)");
269 log_print!(" • Track 3: Treasury Medium Spender");
270 log_print!(" • Track 4: Treasury Big Spender");
271 log_print!(" • Track 5: Treasury Treasurer (highest amounts)");
272 log_print!("");
273 log_print!(
274 "2. {} Submit referendum with Treasury spend call:",
275 "Example".bright_green().bold()
276 );
277 log_print!(
278 " quantus referenda submit-remark --message \"Treasury spend: 1000 QUAN to Alice\""
279 );
280 log_print!(" --from <YOUR_WALLET> --password <PASSWORD>");
281 log_print!("");
282 log_print!(" Note: Use appropriate origin for treasury tracks");
283 log_print!("");
284 log_print!("3. {} Community votes on the proposal", "Voting".bright_magenta().bold());
285 log_print!("");
286 log_print!("4. {} If approved, funds are paid automatically", "Execution".bright_blue().bold());
287 log_print!("");
288 log_print!("💡 {}", "Useful Commands:".bright_cyan().bold());
289 log_print!(" quantus treasury balance - Check Treasury balance");
290 log_print!(" quantus treasury config - View Treasury configuration");
291 log_print!(" quantus referenda config - View available tracks");
292 log_print!("");
293
294 Ok(())
295}
296
297async fn submit_spend_referendum(
299 quantus_client: &crate::chain::client::QuantusClient,
300 beneficiary: &str,
301 amount: &str,
302 track: &str,
303 from: &str,
304 password: Option<String>,
305 password_file: Option<String>,
306 finalized: bool,
307) -> crate::error::Result<()> {
308 use qp_poseidon::PoseidonHasher;
309 use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
310 use subxt::tx::Payload;
311
312 log_print!("💰 Submitting Treasury Spend Referendum");
313 log_print!(" 📍 Beneficiary: {}", beneficiary.bright_yellow());
314 log_print!(" 💵 Amount: {}", amount.bright_green());
315 log_print!(" 🛤️ Track: {}", track.bright_cyan());
316
317 let amount_value = crate::cli::send::parse_amount(quantus_client, amount).await?;
319
320 let beneficiary_resolved = crate::cli::common::resolve_address(beneficiary)?;
322 let (beneficiary_sp, _) = SpAccountId32::from_ss58check_with_version(&beneficiary_resolved)
323 .map_err(|e| {
324 crate::error::QuantusError::Generic(format!(
325 "Invalid beneficiary address '{beneficiary}': {e:?}"
326 ))
327 })?;
328 let bytes: [u8; 32] = *beneficiary_sp.as_ref();
329 let beneficiary_account = subxt::utils::AccountId32::from(bytes);
330
331 let beneficiary_multi = subxt::utils::MultiAddress::Id(beneficiary_account.clone());
333
334 let treasury_spend_call =
335 quantus_subxt::api::tx()
336 .treasury_pallet()
337 .spend((), amount_value, beneficiary_multi, None);
338
339 let encoded_call = treasury_spend_call
341 .encode_call_data(&quantus_client.client().metadata())
342 .map_err(|e| {
343 crate::error::QuantusError::Generic(format!("Failed to encode call: {:?}", e))
344 })?;
345
346 log_print!("📝 Creating preimage...");
347
348 let keypair =
350 crate::wallet::load_keypair_from_wallet(from, password.clone(), password_file.clone())?;
351
352 let preimage_hash: sp_core::H256 =
354 <PoseidonHasher as sp_runtime::traits::Hash>::hash(&encoded_call);
355
356 log_print!("🔗 Preimage hash: {:?}", preimage_hash);
357
358 let preimage_call = quantus_subxt::api::tx().preimage().note_preimage(encoded_call.clone());
360 let preimage_tx_hash =
361 submit_transaction(quantus_client, &keypair, preimage_call, None, finalized).await?;
362
363 log_print!("✅ Preimage created {:?}", preimage_tx_hash);
364
365 let origin_caller = match track.to_lowercase().as_str() {
367 "small" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
368 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::SmallSpender,
369 ),
370 "medium" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
371 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::MediumSpender,
372 ),
373 "big" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
374 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::BigSpender,
375 ),
376 "treasurer" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
377 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::Treasurer,
378 ),
379 _ => {
380 return Err(crate::error::QuantusError::Generic(format!(
381 "Invalid track: {}. Must be 'small', 'medium', 'big', or 'treasurer'",
382 track
383 )))
384 },
385 };
386
387 let proposal =
389 quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded::Lookup {
390 hash: preimage_hash,
391 len: encoded_call.len() as u32,
392 };
393
394 log_print!("📜 Submitting referendum...");
395
396 let enactment =
398 quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
399 1u32,
400 );
401 let submit_call =
402 quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment);
403 let submit_tx_hash =
404 submit_transaction(quantus_client, &keypair, submit_call, None, finalized).await?;
405
406 log_print!(
407 "✅ {} Treasury spend referendum submitted! {:?}",
408 "SUCCESS".bright_green().bold(),
409 submit_tx_hash
410 );
411 log_print!("💡 Next steps:");
412 log_print!(" 1. Place decision deposit: quantus referenda place-decision-deposit --index <INDEX> --from {}", from);
413 log_print!(
414 " 2. Vote on the referendum: quantus referenda vote --index <INDEX> --aye --from <VOTER>"
415 );
416 log_print!(
417 " 3. After approval, payout: quantus treasury payout --index <SPEND_INDEX> --from {}",
418 from
419 );
420
421 Ok(())
422}
423
424async fn payout_spend(
426 quantus_client: &crate::chain::client::QuantusClient,
427 index: u32,
428 from: &str,
429 password: Option<String>,
430 password_file: Option<String>,
431 finalized: bool,
432) -> crate::error::Result<()> {
433 log_print!("💸 Paying out Treasury Spend #{}", index);
434
435 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
437
438 let payout_call = quantus_subxt::api::tx().treasury_pallet().payout(index);
440
441 let tx_hash =
442 submit_transaction(quantus_client, &keypair, payout_call, None, finalized).await?;
443 log_print!(
444 "✅ {} Payout transaction submitted! Hash: {:?}",
445 "SUCCESS".bright_green().bold(),
446 tx_hash
447 );
448
449 log_success!("🎉 {} Treasury spend paid out!", "FINISHED".bright_green().bold());
450 log_print!("💡 Use 'quantus treasury check-status --index {}' to cleanup", index);
451
452 Ok(())
453}
454
455async fn check_spend_status(
457 quantus_client: &crate::chain::client::QuantusClient,
458 index: u32,
459 from: &str,
460 password: Option<String>,
461 password_file: Option<String>,
462 finalized: bool,
463) -> crate::error::Result<()> {
464 log_print!("🔍 Checking Treasury Spend #{} status", index);
465
466 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
468
469 let check_call = quantus_subxt::api::tx().treasury_pallet().check_status(index);
471
472 let tx_hash = submit_transaction(quantus_client, &keypair, check_call, None, finalized).await?;
473 log_print!(
474 "✅ {} Check status transaction submitted! Hash: {:?}",
475 "SUCCESS".bright_green().bold(),
476 tx_hash
477 );
478
479 log_success!("🎉 {} Spend status checked and cleaned up!", "FINISHED".bright_green().bold());
480
481 Ok(())
482}
483
484async fn list_spends(
486 quantus_client: &crate::chain::client::QuantusClient,
487) -> crate::error::Result<()> {
488 log_print!("📋 Active Treasury Spends");
489 log_print!("");
490
491 let latest_block_hash = quantus_client.get_latest_block().await?;
492 let storage_at = quantus_client.client().storage().at(latest_block_hash);
493
494 let mut count = 0;
496 for spend_index in 0..100 {
497 let spend_addr = quantus_subxt::api::storage().treasury_pallet().spends(spend_index);
498 if let Some(spend_status) = storage_at.fetch(&spend_addr).await? {
499 log_print!("💰 Spend #{}", spend_index.to_string().bright_yellow().bold());
500 log_print!(" Amount: {} (raw)", spend_status.amount.to_string().bright_green());
501
502 use crate::cli::address_format::QuantusSS58;
504 let beneficiary_str = spend_status.beneficiary.to_quantus_ss58();
505 log_print!(" Beneficiary: {}", beneficiary_str.bright_cyan());
506 log_print!(" Valid From: Block #{}", spend_status.valid_from);
507 log_print!(" Expires At: Block #{}", spend_status.expire_at);
508 log_print!(
509 " Status: {}",
510 match spend_status.status {
511 quantus_subxt::api::runtime_types::pallet_treasury::PaymentState::Pending =>
512 "Pending".bright_yellow(),
513 quantus_subxt::api::runtime_types::pallet_treasury::PaymentState::Attempted { .. } =>
514 "Attempted".bright_blue(),
515 quantus_subxt::api::runtime_types::pallet_treasury::PaymentState::Failed =>
516 "Failed".bright_red(),
517 }
518 );
519 log_print!("");
520 count += 1;
521 }
522 }
523
524 if count == 0 {
525 log_print!("📭 No active Treasury spends found");
526 } else {
527 log_print!("Total: {} active spend(s)", count.to_string().bright_green().bold());
528 }
529
530 Ok(())
531}
532
533async fn spend_sudo(
535 quantus_client: &crate::chain::client::QuantusClient,
536 beneficiary: &str,
537 amount: &str,
538 from: &str,
539 password: Option<String>,
540 password_file: Option<String>,
541 finalized: bool,
542) -> crate::error::Result<()> {
543 use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
544
545 log_print!("💰 Creating Treasury Spend via Sudo (Root)");
546 log_print!(" 📍 Beneficiary: {}", beneficiary.bright_yellow());
547 log_print!(" 💵 Amount: {}", amount.bright_green());
548 log_print!(" ⚠️ Using ROOT permissions (sudo)");
549
550 let amount_value = crate::cli::send::parse_amount(quantus_client, amount).await?;
552
553 let beneficiary_resolved = crate::cli::common::resolve_address(beneficiary)?;
555 let (beneficiary_sp, _) = SpAccountId32::from_ss58check_with_version(&beneficiary_resolved)
556 .map_err(|e| {
557 crate::error::QuantusError::Generic(format!(
558 "Invalid beneficiary address '{beneficiary}': {e:?}"
559 ))
560 })?;
561 let bytes: [u8; 32] = *beneficiary_sp.as_ref();
562 let beneficiary_account = subxt::utils::AccountId32::from(bytes);
563 let beneficiary_multi = subxt::utils::MultiAddress::Id(beneficiary_account.clone());
564
565 let spend_call = quantus_subxt::api::Call::TreasuryPallet(
567 quantus_subxt::api::treasury_pallet::Call::spend {
568 asset_kind: Box::new(()),
569 amount: amount_value,
570 beneficiary: Box::new(beneficiary_multi),
571 valid_from: None,
572 },
573 );
574
575 let sudo_call = quantus_subxt::api::tx().sudo().sudo(spend_call);
577
578 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
580
581 log_print!("📡 Submitting sudo transaction...");
583 let tx_hash = submit_transaction(quantus_client, &keypair, sudo_call, None, finalized).await?;
584 log_print!(
585 "✅ {} Sudo transaction submitted! Hash: {:?}",
586 "SUCCESS".bright_green().bold(),
587 tx_hash
588 );
589
590 log_success!("🎉 {} Treasury spend created via sudo!", "FINISHED".bright_green().bold());
591 log_print!("💡 Next step: quantus treasury list-spends");
592 log_print!("💡 Then payout: quantus treasury payout --index <INDEX> --from {}", beneficiary);
593
594 Ok(())
595}