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 execution_mode: crate::cli::common::ExecutionMode,
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 execution_mode,
145 )
146 .await,
147 TreasuryCommands::Payout { index, from, password, password_file } =>
148 payout_spend(&quantus_client, index, &from, password, password_file, execution_mode)
149 .await,
150 TreasuryCommands::CheckStatus { index, from, password, password_file } =>
151 check_spend_status(
152 &quantus_client,
153 index,
154 &from,
155 password,
156 password_file,
157 execution_mode,
158 )
159 .await,
160 TreasuryCommands::ListSpends => list_spends(&quantus_client).await,
161 TreasuryCommands::SpendSudo { beneficiary, amount, from, password, password_file } =>
162 spend_sudo(
163 &quantus_client,
164 &beneficiary,
165 &amount,
166 &from,
167 password,
168 password_file,
169 execution_mode,
170 )
171 .await,
172 }
173}
174
175async fn get_treasury_balance(
177 quantus_client: &crate::chain::client::QuantusClient,
178) -> crate::error::Result<()> {
179 log_print!("💰 Treasury Balance");
180 log_print!("");
181
182 let treasury_pallet_id = frame_support::PalletId(*b"py/trsry");
184 let treasury_account_raw: sp_runtime::AccountId32 =
185 treasury_pallet_id.into_account_truncating();
186
187 let treasury_account = subxt::utils::AccountId32(*treasury_account_raw.as_ref());
189
190 let addr = quantus_subxt::api::storage().system().account(treasury_account.clone());
192
193 let latest_block_hash = quantus_client.get_latest_block().await?;
194 let storage_at = quantus_client.client().storage().at(latest_block_hash);
195
196 let account_info = storage_at.fetch(&addr).await?.ok_or_else(|| {
197 crate::error::QuantusError::Generic("Treasury account not found".to_string())
198 })?;
199
200 let free_balance = account_info.data.free;
201 let reserved_balance = account_info.data.reserved;
202
203 let formatted_free_balance =
204 crate::cli::send::format_balance_with_symbol(quantus_client, free_balance).await?;
205 let formatted_reserved_balance =
206 crate::cli::send::format_balance_with_symbol(quantus_client, reserved_balance).await?;
207
208 log_print!("💰 Free Balance: {}", formatted_free_balance);
209 log_print!("💰 Reserved: {}", formatted_reserved_balance);
210
211 use crate::cli::address_format::QuantusSS58;
213 let treasury_address = treasury_account_raw.to_quantus_ss58();
214 log_print!("📍 Treasury Account: {}", treasury_address.bright_yellow());
215
216 Ok(())
217}
218
219async fn get_config(
221 quantus_client: &crate::chain::client::QuantusClient,
222) -> crate::error::Result<()> {
223 log_print!("⚙️ Treasury Configuration");
224 log_print!("");
225
226 let constants = quantus_client.client().constants();
227
228 if let Ok(spend_period) =
230 constants.at(&quantus_subxt::api::constants().treasury_pallet().spend_period())
231 {
232 log_print!("⏰ Spend Period: {} blocks", spend_period.to_string().bright_cyan());
233 let hours = spend_period as f64 * 3.0 / 3600.0; log_print!(" (~{:.1} hours)", hours);
235 }
236
237 if let Ok(burn) = constants.at(&quantus_subxt::api::constants().treasury_pallet().burn()) {
239 log_print!("🔥 Burn: {:?}", burn);
240 }
241
242 if let Ok(max_approvals) =
244 constants.at(&quantus_subxt::api::constants().treasury_pallet().max_approvals())
245 {
246 log_print!("📊 Max Approvals: {}", max_approvals.to_string().bright_yellow());
247 }
248
249 if let Ok(payout_period) =
251 constants.at(&quantus_subxt::api::constants().treasury_pallet().payout_period())
252 {
253 log_print!("💸 Payout Period: {} blocks", payout_period.to_string().bright_green());
254 let days = payout_period as f64 * 3.0 / 86400.0; log_print!(" (~{:.1} days)", days);
256 }
257
258 Ok(())
259}
260
261async fn show_treasury_info() -> crate::error::Result<()> {
263 log_print!("💰 Treasury Information");
264 log_print!("");
265 log_print!("The Treasury is a pot of funds collected through:");
266 log_print!(" • Transaction fees");
267 log_print!(" • Slashing");
268 log_print!(" • Other network mechanisms");
269 log_print!("");
270 log_print!("📋 {} To spend from Treasury:", "HOW TO USE".bright_cyan().bold());
271 log_print!("");
272 log_print!(
273 "1. {} Create a spending proposal using Referenda:",
274 "Treasury Tracks".bright_yellow().bold()
275 );
276 log_print!(" • Track 2: Treasury Small Spender (< certain amount)");
277 log_print!(" • Track 3: Treasury Medium Spender");
278 log_print!(" • Track 4: Treasury Big Spender");
279 log_print!(" • Track 5: Treasury Treasurer (highest amounts)");
280 log_print!("");
281 log_print!(
282 "2. {} Submit referendum with Treasury spend call:",
283 "Example".bright_green().bold()
284 );
285 log_print!(
286 " quantus referenda submit-remark --message \"Treasury spend: 1000 QUAN to Alice\""
287 );
288 log_print!(" --from <YOUR_WALLET> --password <PASSWORD>");
289 log_print!("");
290 log_print!(" Note: Use appropriate origin for treasury tracks");
291 log_print!("");
292 log_print!("3. {} Community votes on the proposal", "Voting".bright_magenta().bold());
293 log_print!("");
294 log_print!("4. {} If approved, funds are paid automatically", "Execution".bright_blue().bold());
295 log_print!("");
296 log_print!("💡 {}", "Useful Commands:".bright_cyan().bold());
297 log_print!(" quantus treasury balance - Check Treasury balance");
298 log_print!(" quantus treasury config - View Treasury configuration");
299 log_print!(" quantus referenda config - View available tracks");
300 log_print!("");
301
302 Ok(())
303}
304
305async fn submit_spend_referendum(
307 quantus_client: &crate::chain::client::QuantusClient,
308 beneficiary: &str,
309 amount: &str,
310 track: &str,
311 from: &str,
312 password: Option<String>,
313 password_file: Option<String>,
314 execution_mode: crate::cli::common::ExecutionMode,
315) -> crate::error::Result<()> {
316 use qp_poseidon::PoseidonHasher;
317 use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
318 use subxt::tx::Payload;
319
320 log_print!("💰 Submitting Treasury Spend Referendum");
321 log_print!(" 📍 Beneficiary: {}", beneficiary.bright_yellow());
322 log_print!(" 💵 Amount: {}", amount.bright_green());
323 log_print!(" 🛤️ Track: {}", track.bright_cyan());
324
325 let amount_value = crate::cli::send::parse_amount(quantus_client, amount).await?;
327
328 let beneficiary_resolved = crate::cli::common::resolve_address(beneficiary)?;
330 let (beneficiary_sp, _) = SpAccountId32::from_ss58check_with_version(&beneficiary_resolved)
331 .map_err(|e| {
332 crate::error::QuantusError::Generic(format!(
333 "Invalid beneficiary address '{beneficiary}': {e:?}"
334 ))
335 })?;
336 let bytes: [u8; 32] = *beneficiary_sp.as_ref();
337 let beneficiary_account = subxt::utils::AccountId32::from(bytes);
338
339 let beneficiary_multi = subxt::utils::MultiAddress::Id(beneficiary_account.clone());
341
342 let treasury_spend_call =
343 quantus_subxt::api::tx()
344 .treasury_pallet()
345 .spend((), amount_value, beneficiary_multi, None);
346
347 let encoded_call = treasury_spend_call
349 .encode_call_data(&quantus_client.client().metadata())
350 .map_err(|e| {
351 crate::error::QuantusError::Generic(format!("Failed to encode call: {:?}", e))
352 })?;
353
354 log_print!("📝 Creating preimage...");
355
356 let keypair =
358 crate::wallet::load_keypair_from_wallet(from, password.clone(), password_file.clone())?;
359
360 let preimage_hash: sp_core::H256 =
362 <PoseidonHasher as sp_runtime::traits::Hash>::hash(&encoded_call);
363
364 log_print!("🔗 Preimage hash: {:?}", preimage_hash);
365
366 let preimage_call = quantus_subxt::api::tx().preimage().note_preimage(encoded_call.clone());
368 let preimage_tx_hash =
369 submit_transaction(quantus_client, &keypair, preimage_call, None, execution_mode).await?;
370
371 log_print!("✅ Preimage created {:?}", preimage_tx_hash);
372
373 let origin_caller = match track.to_lowercase().as_str() {
375 "small" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
376 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::SmallSpender,
377 ),
378 "medium" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
379 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::MediumSpender,
380 ),
381 "big" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
382 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::BigSpender,
383 ),
384 "treasurer" => quantus_subxt::api::runtime_types::quantus_runtime::OriginCaller::Origins(
385 quantus_subxt::api::runtime_types::quantus_runtime::governance::origins::pallet_custom_origins::Origin::Treasurer,
386 ),
387 _ => {
388 return Err(crate::error::QuantusError::Generic(format!(
389 "Invalid track: {}. Must be 'small', 'medium', 'big', or 'treasurer'",
390 track
391 )))
392 },
393 };
394
395 let proposal =
397 quantus_subxt::api::runtime_types::frame_support::traits::preimages::Bounded::Lookup {
398 hash: preimage_hash,
399 len: encoded_call.len() as u32,
400 };
401
402 log_print!("📜 Submitting referendum...");
403
404 let enactment =
406 quantus_subxt::api::runtime_types::frame_support::traits::schedule::DispatchTime::After(
407 1u32,
408 );
409 let submit_call =
410 quantus_subxt::api::tx().referenda().submit(origin_caller, proposal, enactment);
411 let submit_tx_hash =
412 submit_transaction(quantus_client, &keypair, submit_call, None, execution_mode).await?;
413
414 log_print!(
415 "✅ {} Treasury spend referendum submitted! {:?}",
416 "SUCCESS".bright_green().bold(),
417 submit_tx_hash
418 );
419 log_print!("💡 Next steps:");
420 log_print!(" 1. Place decision deposit: quantus referenda place-decision-deposit --index <INDEX> --from {}", from);
421 log_print!(
422 " 2. Vote on the referendum: quantus referenda vote --index <INDEX> --aye --from <VOTER>"
423 );
424 log_print!(
425 " 3. After approval, payout: quantus treasury payout --index <SPEND_INDEX> --from {}",
426 from
427 );
428
429 Ok(())
430}
431
432async fn payout_spend(
434 quantus_client: &crate::chain::client::QuantusClient,
435 index: u32,
436 from: &str,
437 password: Option<String>,
438 password_file: Option<String>,
439 execution_mode: crate::cli::common::ExecutionMode,
440) -> crate::error::Result<()> {
441 log_print!("💸 Paying out Treasury Spend #{}", index);
442
443 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
445
446 let payout_call = quantus_subxt::api::tx().treasury_pallet().payout(index);
448
449 let tx_hash =
450 submit_transaction(quantus_client, &keypair, payout_call, None, execution_mode).await?;
451 log_print!(
452 "✅ {} Payout transaction submitted! Hash: {:?}",
453 "SUCCESS".bright_green().bold(),
454 tx_hash
455 );
456
457 log_success!("🎉 {} Treasury spend paid out!", "FINISHED".bright_green().bold());
458 log_print!("💡 Use 'quantus treasury check-status --index {}' to cleanup", index);
459
460 Ok(())
461}
462
463async fn check_spend_status(
465 quantus_client: &crate::chain::client::QuantusClient,
466 index: u32,
467 from: &str,
468 password: Option<String>,
469 password_file: Option<String>,
470 execution_mode: crate::cli::common::ExecutionMode,
471) -> crate::error::Result<()> {
472 log_print!("🔍 Checking Treasury Spend #{} status", index);
473
474 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
476
477 let check_call = quantus_subxt::api::tx().treasury_pallet().check_status(index);
479
480 let tx_hash =
481 submit_transaction(quantus_client, &keypair, check_call, None, execution_mode).await?;
482 log_print!(
483 "✅ {} Check status transaction submitted! Hash: {:?}",
484 "SUCCESS".bright_green().bold(),
485 tx_hash
486 );
487
488 log_success!("🎉 {} Spend status checked and cleaned up!", "FINISHED".bright_green().bold());
489
490 Ok(())
491}
492
493async fn list_spends(
495 quantus_client: &crate::chain::client::QuantusClient,
496) -> crate::error::Result<()> {
497 log_print!("📋 Active Treasury Spends");
498 log_print!("");
499
500 let latest_block_hash = quantus_client.get_latest_block().await?;
501 let storage_at = quantus_client.client().storage().at(latest_block_hash);
502
503 let mut count = 0;
505 for spend_index in 0..100 {
506 let spend_addr = quantus_subxt::api::storage().treasury_pallet().spends(spend_index);
507 if let Some(spend_status) = storage_at.fetch(&spend_addr).await? {
508 log_print!("💰 Spend #{}", spend_index.to_string().bright_yellow().bold());
509 log_print!(" Amount: {} (raw)", spend_status.amount.to_string().bright_green());
510
511 use crate::cli::address_format::QuantusSS58;
513 let beneficiary_str = spend_status.beneficiary.to_quantus_ss58();
514 log_print!(" Beneficiary: {}", beneficiary_str.bright_cyan());
515 log_print!(" Valid From: Block #{}", spend_status.valid_from);
516 log_print!(" Expires At: Block #{}", spend_status.expire_at);
517 log_print!(
518 " Status: {}",
519 match spend_status.status {
520 quantus_subxt::api::runtime_types::pallet_treasury::PaymentState::Pending =>
521 "Pending".bright_yellow(),
522 quantus_subxt::api::runtime_types::pallet_treasury::PaymentState::Attempted { .. } =>
523 "Attempted".bright_blue(),
524 quantus_subxt::api::runtime_types::pallet_treasury::PaymentState::Failed =>
525 "Failed".bright_red(),
526 }
527 );
528 log_print!("");
529 count += 1;
530 }
531 }
532
533 if count == 0 {
534 log_print!("📭 No active Treasury spends found");
535 } else {
536 log_print!("Total: {} active spend(s)", count.to_string().bright_green().bold());
537 }
538
539 Ok(())
540}
541
542async fn spend_sudo(
544 quantus_client: &crate::chain::client::QuantusClient,
545 beneficiary: &str,
546 amount: &str,
547 from: &str,
548 password: Option<String>,
549 password_file: Option<String>,
550 execution_mode: crate::cli::common::ExecutionMode,
551) -> crate::error::Result<()> {
552 use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
553
554 log_print!("💰 Creating Treasury Spend via Sudo (Root)");
555 log_print!(" 📍 Beneficiary: {}", beneficiary.bright_yellow());
556 log_print!(" 💵 Amount: {}", amount.bright_green());
557 log_print!(" ⚠️ Using ROOT permissions (sudo)");
558
559 let amount_value = crate::cli::send::parse_amount(quantus_client, amount).await?;
561
562 let beneficiary_resolved = crate::cli::common::resolve_address(beneficiary)?;
564 let (beneficiary_sp, _) = SpAccountId32::from_ss58check_with_version(&beneficiary_resolved)
565 .map_err(|e| {
566 crate::error::QuantusError::Generic(format!(
567 "Invalid beneficiary address '{beneficiary}': {e:?}"
568 ))
569 })?;
570 let bytes: [u8; 32] = *beneficiary_sp.as_ref();
571 let beneficiary_account = subxt::utils::AccountId32::from(bytes);
572 let beneficiary_multi = subxt::utils::MultiAddress::Id(beneficiary_account.clone());
573
574 let spend_call = quantus_subxt::api::Call::TreasuryPallet(
576 quantus_subxt::api::treasury_pallet::Call::spend {
577 asset_kind: Box::new(()),
578 amount: amount_value,
579 beneficiary: Box::new(beneficiary_multi),
580 valid_from: None,
581 },
582 );
583
584 let sudo_call = quantus_subxt::api::tx().sudo().sudo(spend_call);
586
587 let keypair = crate::wallet::load_keypair_from_wallet(from, password, password_file)?;
589
590 log_print!("📡 Submitting sudo transaction...");
592 let tx_hash =
593 submit_transaction(quantus_client, &keypair, sudo_call, None, execution_mode).await?;
594 log_print!(
595 "✅ {} Sudo transaction submitted! Hash: {:?}",
596 "SUCCESS".bright_green().bold(),
597 tx_hash
598 );
599
600 log_success!("🎉 {} Treasury spend created via sudo!", "FINISHED".bright_green().bold());
601 log_print!("💡 Next step: quantus treasury list-spends");
602 log_print!("💡 Then payout: quantus treasury payout --index <INDEX> --from {}", beneficiary);
603
604 Ok(())
605}