1use crate::{
3 chain::quantus_subxt,
4 cli::{address_format::QuantusSS58, common::resolve_address},
5 error::QuantusError,
6 log_print, log_success, log_verbose,
7};
8use clap::Subcommand;
9use colored::Colorize;
10use sp_core::crypto::{AccountId32, Ss58Codec};
11
12#[derive(Subcommand, Debug)]
14pub enum TechCollectiveCommands {
15 AddMember {
17 #[arg(short, long)]
19 who: String,
20
21 #[arg(short, long)]
23 from: String,
24
25 #[arg(short, long)]
27 password: Option<String>,
28
29 #[arg(long)]
31 password_file: Option<String>,
32 },
33
34 RemoveMember {
36 #[arg(short, long)]
38 who: String,
39
40 #[arg(short, long)]
42 from: String,
43
44 #[arg(short, long)]
46 password: Option<String>,
47
48 #[arg(long)]
50 password_file: Option<String>,
51 },
52
53 Vote {
55 #[arg(short, long)]
57 referendum_index: u32,
58
59 #[arg(short, long)]
61 aye: bool,
62
63 #[arg(short, long)]
65 from: String,
66
67 #[arg(short, long)]
69 password: Option<String>,
70
71 #[arg(long)]
73 password_file: Option<String>,
74 },
75
76 ListMembers,
78
79 IsMember {
81 #[arg(short, long)]
83 address: String,
84 },
85
86 CheckSudo {
88 #[arg(short, long)]
90 address: Option<String>,
91 },
92
93 ListReferenda,
95
96 GetReferendum {
98 #[arg(short, long)]
100 index: u32,
101 },
102}
103
104pub async fn add_member(
106 quantus_client: &crate::chain::client::QuantusClient,
107 from_keypair: &crate::wallet::QuantumKeyPair,
108 who_address: &str,
109 finalized: bool,
110) -> crate::error::Result<subxt::utils::H256> {
111 log_verbose!("🏛️ Adding member to Tech Collective...");
112 log_verbose!(" Member: {}", who_address.bright_cyan());
113
114 let (member_account_sp, _) = AccountId32::from_ss58check_with_version(who_address)
116 .map_err(|e| QuantusError::Generic(format!("Invalid member address: {e:?}")))?;
117
118 let member_account_bytes: [u8; 32] = *member_account_sp.as_ref();
120 let member_account_id = subxt::ext::subxt_core::utils::AccountId32::from(member_account_bytes);
121
122 log_verbose!("✍️ Creating add_member transaction...");
123
124 let add_member_call = quantus_subxt::api::Call::TechCollective(
126 quantus_subxt::api::tech_collective::Call::add_member {
127 who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id),
128 },
129 );
130
131 let sudo_call = quantus_subxt::api::tx().sudo().sudo(add_member_call);
133
134 let tx_hash = crate::cli::common::submit_transaction(
135 quantus_client,
136 from_keypair,
137 sudo_call,
138 None,
139 finalized,
140 )
141 .await?;
142
143 log_verbose!("📋 Add member transaction submitted: {:?}", tx_hash);
144
145 Ok(tx_hash)
146}
147
148pub async fn remove_member(
150 quantus_client: &crate::chain::client::QuantusClient,
151 from_keypair: &crate::wallet::QuantumKeyPair,
152 who_address: &str,
153 finalized: bool,
154) -> crate::error::Result<subxt::utils::H256> {
155 log_verbose!("🏛️ Removing member from Tech Collective...");
156 log_verbose!(" Member: {}", who_address.bright_cyan());
157
158 let (member_account_sp, _) = AccountId32::from_ss58check_with_version(who_address)
160 .map_err(|e| QuantusError::Generic(format!("Invalid member address: {e:?}")))?;
161
162 let member_account_bytes: [u8; 32] = *member_account_sp.as_ref();
164 let member_account_id = subxt::ext::subxt_core::utils::AccountId32::from(member_account_bytes);
165
166 log_verbose!("✍️ Creating remove_member transaction...");
167
168 let remove_member_call = quantus_subxt::api::Call::TechCollective(
170 quantus_subxt::api::tech_collective::Call::remove_member {
171 who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id),
172 min_rank: 0u16, },
174 );
175
176 let sudo_call = quantus_subxt::api::tx().sudo().sudo(remove_member_call);
178
179 let tx_hash = crate::cli::common::submit_transaction(
180 quantus_client,
181 from_keypair,
182 sudo_call,
183 None,
184 finalized,
185 )
186 .await?;
187
188 log_verbose!("📋 Remove member transaction submitted: {:?}", tx_hash);
189
190 Ok(tx_hash)
191}
192
193pub async fn vote_on_referendum(
195 quantus_client: &crate::chain::client::QuantusClient,
196 from_keypair: &crate::wallet::QuantumKeyPair,
197 referendum_index: u32,
198 aye: bool,
199 finalized: bool,
200) -> crate::error::Result<subxt::utils::H256> {
201 log_verbose!("🗳️ Voting on referendum...");
202 log_verbose!(" Referendum: {}", referendum_index);
203 log_verbose!(" Vote: {}", if aye { "AYE" } else { "NAY" });
204
205 log_verbose!("✍️ Creating vote transaction...");
206
207 let vote_call = quantus_subxt::api::tx().tech_collective().vote(referendum_index, aye);
209
210 let tx_hash = crate::cli::common::submit_transaction(
211 quantus_client,
212 from_keypair,
213 vote_call,
214 None,
215 finalized,
216 )
217 .await?;
218
219 log_verbose!("📋 Vote transaction submitted: {:?}", tx_hash);
220
221 Ok(tx_hash)
222}
223
224pub async fn is_member(
226 quantus_client: &crate::chain::client::QuantusClient,
227 address: &str,
228) -> crate::error::Result<bool> {
229 log_verbose!("🔍 Checking membership...");
230 log_verbose!(" Address: {}", address.bright_cyan());
231
232 let (account_sp, _) = AccountId32::from_ss58check_with_version(address)
234 .map_err(|e| QuantusError::Generic(format!("Invalid address: {e:?}")))?;
235
236 let account_bytes: [u8; 32] = *account_sp.as_ref();
238 let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
239
240 let storage_addr = quantus_subxt::api::storage().tech_collective().members(account_id);
242
243 let latest_block_hash = quantus_client.get_latest_block().await?;
245
246 let storage_at = quantus_client.client().storage().at(latest_block_hash);
247
248 let member_data = storage_at
249 .fetch(&storage_addr)
250 .await
251 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch member data: {e:?}")))?;
252
253 Ok(member_data.is_some())
254}
255
256pub async fn get_member_count(
258 quantus_client: &crate::chain::client::QuantusClient,
259) -> crate::error::Result<Option<u32>> {
260 log_verbose!("🔍 Getting member count...");
261
262 let storage_addr = quantus_subxt::api::storage().tech_collective().member_count(0u16);
264
265 let latest_block_hash = quantus_client.get_latest_block().await?;
267
268 let storage_at = quantus_client.client().storage().at(latest_block_hash);
269
270 let count_data = storage_at
271 .fetch(&storage_addr)
272 .await
273 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch member count: {e:?}")))?;
274
275 Ok(count_data)
276}
277
278pub async fn get_member_list(
280 quantus_client: &crate::chain::client::QuantusClient,
281) -> crate::error::Result<Vec<AccountId32>> {
282 log_verbose!("🔍 Getting member list...");
283
284 let latest_block_hash = quantus_client.get_latest_block().await?;
286
287 let storage_at = quantus_client.client().storage().at(latest_block_hash);
288
289 let members_storage = quantus_subxt::api::storage().tech_collective().members_iter();
291
292 let mut members = Vec::new();
293 let mut iter = storage_at.iter(members_storage).await.map_err(|e| {
294 QuantusError::NetworkError(format!("Failed to create members iterator: {e:?}"))
295 })?;
296
297 while let Some(result) = iter.next().await {
298 match result {
299 Ok(storage_entry) => {
300 let key = storage_entry.key_bytes;
301 if key.len() >= 32 {
304 let account_bytes: [u8; 32] =
306 key[key.len() - 32..].try_into().unwrap_or([0u8; 32]);
307 let sp_account = AccountId32::from(account_bytes);
308 log_verbose!("Found member: {}", sp_account.to_quantus_ss58());
309 members.push(sp_account);
310 }
311 },
312 Err(e) => {
313 log_verbose!("⚠️ Error reading member entry: {:?}", e);
314 },
315 }
316 }
317
318 log_verbose!("Found {} total members", members.len());
319 Ok(members)
320}
321
322pub async fn get_sudo_account(
324 quantus_client: &crate::chain::client::QuantusClient,
325) -> crate::error::Result<Option<AccountId32>> {
326 log_verbose!("🔍 Getting sudo account...");
327
328 let storage_addr = quantus_subxt::api::storage().sudo().key();
330
331 let latest_block_hash = quantus_client.get_latest_block().await?;
333
334 let storage_at = quantus_client.client().storage().at(latest_block_hash);
335
336 let sudo_account = storage_at
337 .fetch(&storage_addr)
338 .await
339 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch sudo account: {e:?}")))?;
340
341 if let Some(subxt_account) = sudo_account {
343 let account_bytes: [u8; 32] = *subxt_account.as_ref();
344 let sp_account = AccountId32::from(account_bytes);
345 Ok(Some(sp_account))
346 } else {
347 Ok(None)
348 }
349}
350
351pub async fn handle_tech_collective_command(
353 command: TechCollectiveCommands,
354 node_url: &str,
355 finalized: bool,
356) -> crate::error::Result<()> {
357 log_print!("🏛️ Tech Collective");
358
359 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
360
361 match command {
362 TechCollectiveCommands::AddMember { who, from, password, password_file } => {
363 log_print!("🏛️ Adding member to Tech Collective");
364 log_print!(" 👤 Member: {}", who.bright_cyan());
365 log_print!(" 🔑 Signed by: {}", from.bright_yellow());
366
367 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
369
370 let tx_hash = add_member(&quantus_client, &keypair, &who, finalized).await?;
372
373 log_print!(
374 "✅ {} Add member transaction submitted! Hash: {:?}",
375 "SUCCESS".bright_green().bold(),
376 tx_hash
377 );
378 },
379
380 TechCollectiveCommands::RemoveMember { who, from, password, password_file } => {
381 log_print!("🏛️ Removing member from Tech Collective ");
382 log_print!(" 👤 Member: {}", who.bright_cyan());
383 log_print!(" 🔑 Signed by: {}", from.bright_yellow());
384
385 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
387
388 let tx_hash = remove_member(&quantus_client, &keypair, &who, finalized).await?;
390
391 log_print!(
392 "✅ {} Remove member transaction submitted! Hash: {:?}",
393 "SUCCESS".bright_green().bold(),
394 tx_hash
395 );
396 },
397
398 TechCollectiveCommands::Vote { referendum_index, aye, from, password, password_file } => {
399 log_print!("🗳️ Voting on Tech Referendum #{} ", referendum_index);
400 log_print!(
401 " 📊 Vote: {}",
402 if aye { "AYE ✅".bright_green() } else { "NAY ❌".bright_red() }
403 );
404 log_print!(" 🔑 Signed by: {}", from.bright_yellow());
405
406 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
408
409 let tx_hash =
411 vote_on_referendum(&quantus_client, &keypair, referendum_index, aye, finalized)
412 .await?;
413
414 log_print!(
415 "✅ {} Vote transaction submitted! Hash: {:?}",
416 "SUCCESS".bright_green().bold(),
417 tx_hash
418 );
419 },
420
421 TechCollectiveCommands::ListMembers => {
422 log_print!("🏛️ Tech Collective Members ");
423 log_print!("");
424
425 match get_member_list(&quantus_client).await {
427 Ok(members) =>
428 if members.is_empty() {
429 log_print!("📭 No members in Tech Collective");
430 } else {
431 log_print!("👥 Total members: {}", members.len());
432 log_print!("");
433
434 for (index, member) in members.iter().enumerate() {
435 log_print!(
436 "{}. {}",
437 (index + 1).to_string().bright_blue(),
438 member.to_quantus_ss58().bright_green()
439 );
440 }
441 },
442 Err(e) => {
443 log_verbose!("⚠️ Failed to get member list: {:?}", e);
444 match get_member_count(&quantus_client).await? {
446 Some(count_data) => {
447 log_verbose!("✅ Got member count data: {:?}", count_data);
448 if count_data > 0 {
449 log_print!(
450 "👥 Total members: {} (detailed list unavailable)",
451 count_data
452 );
453 } else {
454 log_print!("📭 No members in Tech Collective");
455 }
456 },
457 None => {
458 log_print!("📭 No member data found - Tech Collective may be empty");
459 },
460 }
461 },
462 }
463
464 log_print!("");
465 log_print!("💡 To check specific membership:");
466 log_print!(" quantus tech-collective is-member --address <ADDRESS>");
467 log_print!("💡 To add a member (requires sudo):");
468 log_print!(
469 " quantus tech-collective add-member --who <ADDRESS> --from <SUDO_WALLET>"
470 );
471 },
472
473 TechCollectiveCommands::IsMember { address } => {
474 log_print!("🔍 Checking Tech Collective membership ");
475
476 let resolved_address = resolve_address(&address)?;
478
479 log_print!(" 👤 Address: {}", resolved_address.bright_cyan());
480
481 if is_member(&quantus_client, &resolved_address).await? {
482 log_success!("✅ Address IS a member of Tech Collective!");
483 log_print!("👥 Member data found in storage");
484 } else {
485 log_print!("❌ Address is NOT a member of Tech Collective");
486 log_print!("💡 No membership record found for this address");
487 }
488 },
489
490 TechCollectiveCommands::CheckSudo { address } => {
491 log_print!("🏛️ Checking sudo permissions ");
492
493 match get_sudo_account(&quantus_client).await? {
494 Some(sudo_account) => {
495 let sudo_address = sudo_account.to_quantus_ss58();
496 log_verbose!("🔍 Found sudo account: {}", sudo_address);
497 log_success!("✅ Found sudo account: {}", sudo_address.bright_green());
498
499 if let Some(check_address) = address {
501 log_verbose!("🔍 Checking if provided address is sudo...");
502
503 let resolved_address = resolve_address(&check_address)?;
505 log_verbose!(" 👤 Address to check: {}", resolved_address);
506
507 if sudo_address == resolved_address {
508 log_success!("✅ Provided address IS the sudo account!");
509 } else {
510 log_print!("❌ Provided address is NOT the sudo account");
511 log_verbose!("💡 Provided address: {}", resolved_address);
512 log_verbose!("💡 Actual sudo address: {}", sudo_address);
513 }
514 } else {
515 log_verbose!("💡 Use 'quantus tech-collective check-sudo --address <ADDRESS>' to check if a specific address is sudo");
517 }
518 },
519 None => {
520 log_print!("📭 No sudo account found in network");
521 log_verbose!("💡 The network may not have sudo configured");
522 },
523 }
524 },
525
526 TechCollectiveCommands::ListReferenda => {
527 log_print!("📜 Active Tech Referenda ");
528 log_print!("");
529
530 log_print!("💡 Referenda listing requires TechReferenda pallet storage queries");
531 log_print!(
532 "💡 Use 'quantus call --pallet TechReferenda --call <method>' for direct interaction"
533 );
534 },
535
536 TechCollectiveCommands::GetReferendum { index } => {
537 log_print!("📄 Tech Referendum #{} Details ", index);
538 log_print!("");
539
540 log_print!("💡 Referendum details require TechReferenda storage access");
541 log_print!("💡 Query ReferendumInfoFor storage with index {}", index);
542 },
543 };
544
545 Ok(())
546}