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