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) -> crate::error::Result<subxt::utils::H256> {
110 log_verbose!("🏛️ Adding member to Tech Collective...");
111 log_verbose!(" Member: {}", who_address.bright_cyan());
112
113 let (member_account_sp, _) = AccountId32::from_ss58check_with_version(who_address)
115 .map_err(|e| QuantusError::Generic(format!("Invalid member address: {e:?}")))?;
116
117 let member_account_bytes: [u8; 32] = *member_account_sp.as_ref();
119 let member_account_id = subxt::ext::subxt_core::utils::AccountId32::from(member_account_bytes);
120
121 log_verbose!("✍️ Creating add_member transaction...");
122
123 let add_member_call = quantus_subxt::api::Call::TechCollective(
125 quantus_subxt::api::tech_collective::Call::add_member {
126 who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id),
127 },
128 );
129
130 let sudo_call = quantus_subxt::api::tx().sudo().sudo(add_member_call);
132
133 let tx_hash =
134 crate::cli::common::submit_transaction(quantus_client, from_keypair, sudo_call, None)
135 .await?;
136
137 log_verbose!("📋 Add member transaction submitted: {:?}", tx_hash);
138
139 Ok(tx_hash)
140}
141
142pub async fn remove_member(
144 quantus_client: &crate::chain::client::QuantusClient,
145 from_keypair: &crate::wallet::QuantumKeyPair,
146 who_address: &str,
147) -> crate::error::Result<subxt::utils::H256> {
148 log_verbose!("🏛️ Removing member from Tech Collective...");
149 log_verbose!(" Member: {}", who_address.bright_cyan());
150
151 let (member_account_sp, _) = AccountId32::from_ss58check_with_version(who_address)
153 .map_err(|e| QuantusError::Generic(format!("Invalid member address: {e:?}")))?;
154
155 let member_account_bytes: [u8; 32] = *member_account_sp.as_ref();
157 let member_account_id = subxt::ext::subxt_core::utils::AccountId32::from(member_account_bytes);
158
159 log_verbose!("✍️ Creating remove_member transaction...");
160
161 let remove_member_call = quantus_subxt::api::Call::TechCollective(
163 quantus_subxt::api::tech_collective::Call::remove_member {
164 who: subxt::ext::subxt_core::utils::MultiAddress::Id(member_account_id),
165 min_rank: 0u16, },
167 );
168
169 let sudo_call = quantus_subxt::api::tx().sudo().sudo(remove_member_call);
171
172 let tx_hash =
173 crate::cli::common::submit_transaction(quantus_client, from_keypair, sudo_call, None)
174 .await?;
175
176 log_verbose!("📋 Remove member transaction submitted: {:?}", tx_hash);
177
178 Ok(tx_hash)
179}
180
181pub async fn vote_on_referendum(
183 quantus_client: &crate::chain::client::QuantusClient,
184 from_keypair: &crate::wallet::QuantumKeyPair,
185 referendum_index: u32,
186 aye: bool,
187) -> crate::error::Result<subxt::utils::H256> {
188 log_verbose!("🗳️ Voting on referendum...");
189 log_verbose!(" Referendum: {}", referendum_index);
190 log_verbose!(" Vote: {}", if aye { "AYE" } else { "NAY" });
191
192 log_verbose!("✍️ Creating vote transaction...");
193
194 let vote_call = quantus_subxt::api::tx().tech_collective().vote(referendum_index, aye);
196
197 let tx_hash =
198 crate::cli::common::submit_transaction(quantus_client, from_keypair, vote_call, None)
199 .await?;
200
201 log_verbose!("📋 Vote transaction submitted: {:?}", tx_hash);
202
203 Ok(tx_hash)
204}
205
206pub async fn is_member(
208 quantus_client: &crate::chain::client::QuantusClient,
209 address: &str,
210) -> crate::error::Result<bool> {
211 log_verbose!("🔍 Checking membership...");
212 log_verbose!(" Address: {}", address.bright_cyan());
213
214 let (account_sp, _) = AccountId32::from_ss58check_with_version(address)
216 .map_err(|e| QuantusError::Generic(format!("Invalid address: {e:?}")))?;
217
218 let account_bytes: [u8; 32] = *account_sp.as_ref();
220 let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
221
222 let storage_addr = quantus_subxt::api::storage().tech_collective().members(account_id);
224
225 let latest_block_hash = quantus_client.get_latest_block().await?;
227
228 let storage_at = quantus_client.client().storage().at(latest_block_hash);
229
230 let member_data = storage_at
231 .fetch(&storage_addr)
232 .await
233 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch member data: {e:?}")))?;
234
235 Ok(member_data.is_some())
236}
237
238pub async fn get_member_count(
240 quantus_client: &crate::chain::client::QuantusClient,
241) -> crate::error::Result<Option<u32>> {
242 log_verbose!("🔍 Getting member count...");
243
244 let storage_addr = quantus_subxt::api::storage().tech_collective().member_count(0u16);
246
247 let latest_block_hash = quantus_client.get_latest_block().await?;
249
250 let storage_at = quantus_client.client().storage().at(latest_block_hash);
251
252 let count_data = storage_at
253 .fetch(&storage_addr)
254 .await
255 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch member count: {e:?}")))?;
256
257 Ok(count_data)
258}
259
260pub async fn get_member_list(
262 quantus_client: &crate::chain::client::QuantusClient,
263) -> crate::error::Result<Vec<AccountId32>> {
264 log_verbose!("🔍 Getting member list...");
265
266 let latest_block_hash = quantus_client.get_latest_block().await?;
268
269 let storage_at = quantus_client.client().storage().at(latest_block_hash);
270
271 let members_storage = quantus_subxt::api::storage().tech_collective().members_iter();
273
274 let mut members = Vec::new();
275 let mut iter = storage_at.iter(members_storage).await.map_err(|e| {
276 QuantusError::NetworkError(format!("Failed to create members iterator: {e:?}"))
277 })?;
278
279 while let Some(result) = iter.next().await {
280 match result {
281 Ok(storage_entry) => {
282 let key = storage_entry.key_bytes;
283 if key.len() >= 32 {
286 let account_bytes: [u8; 32] =
288 key[key.len() - 32..].try_into().unwrap_or([0u8; 32]);
289 let sp_account = AccountId32::from(account_bytes);
290 log_verbose!("Found member: {}", sp_account.to_quantus_ss58());
291 members.push(sp_account);
292 }
293 },
294 Err(e) => {
295 log_verbose!("⚠️ Error reading member entry: {:?}", e);
296 },
297 }
298 }
299
300 log_verbose!("Found {} total members", members.len());
301 Ok(members)
302}
303
304pub async fn get_sudo_account(
306 quantus_client: &crate::chain::client::QuantusClient,
307) -> crate::error::Result<Option<AccountId32>> {
308 log_verbose!("🔍 Getting sudo account...");
309
310 let storage_addr = quantus_subxt::api::storage().sudo().key();
312
313 let latest_block_hash = quantus_client.get_latest_block().await?;
315
316 let storage_at = quantus_client.client().storage().at(latest_block_hash);
317
318 let sudo_account = storage_at
319 .fetch(&storage_addr)
320 .await
321 .map_err(|e| QuantusError::NetworkError(format!("Failed to fetch sudo account: {e:?}")))?;
322
323 if let Some(subxt_account) = sudo_account {
325 let account_bytes: [u8; 32] = *subxt_account.as_ref();
326 let sp_account = AccountId32::from(account_bytes);
327 Ok(Some(sp_account))
328 } else {
329 Ok(None)
330 }
331}
332
333pub async fn handle_tech_collective_command(
335 command: TechCollectiveCommands,
336 node_url: &str,
337) -> crate::error::Result<()> {
338 log_print!("🏛️ Tech Collective");
339
340 let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
341
342 match command {
343 TechCollectiveCommands::AddMember { who, from, password, password_file } => {
344 log_print!("🏛️ Adding member to Tech Collective");
345 log_print!(" 👤 Member: {}", who.bright_cyan());
346 log_print!(" 🔑 Signed by: {}", from.bright_yellow());
347
348 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
350
351 let tx_hash = add_member(&quantus_client, &keypair, &who).await?;
353
354 log_print!(
355 "✅ {} Add member transaction submitted! Hash: {:?}",
356 "SUCCESS".bright_green().bold(),
357 tx_hash
358 );
359 },
360
361 TechCollectiveCommands::RemoveMember { who, from, password, password_file } => {
362 log_print!("🏛️ Removing member from Tech Collective ");
363 log_print!(" 👤 Member: {}", who.bright_cyan());
364 log_print!(" 🔑 Signed by: {}", from.bright_yellow());
365
366 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
368
369 let tx_hash = remove_member(&quantus_client, &keypair, &who).await?;
371
372 log_print!(
373 "✅ {} Remove member transaction submitted! Hash: {:?}",
374 "SUCCESS".bright_green().bold(),
375 tx_hash
376 );
377 },
378
379 TechCollectiveCommands::Vote { referendum_index, aye, from, password, password_file } => {
380 log_print!("🗳️ Voting on Tech Referendum #{} ", referendum_index);
381 log_print!(
382 " 📊 Vote: {}",
383 if aye { "AYE ✅".bright_green() } else { "NAY ❌".bright_red() }
384 );
385 log_print!(" 🔑 Signed by: {}", from.bright_yellow());
386
387 let keypair = crate::wallet::load_keypair_from_wallet(&from, password, password_file)?;
389
390 let tx_hash =
392 vote_on_referendum(&quantus_client, &keypair, referendum_index, aye).await?;
393
394 log_print!(
395 "✅ {} Vote transaction submitted! Hash: {:?}",
396 "SUCCESS".bright_green().bold(),
397 tx_hash
398 );
399 },
400
401 TechCollectiveCommands::ListMembers => {
402 log_print!("🏛️ Tech Collective Members ");
403 log_print!("");
404
405 match get_member_list(&quantus_client).await {
407 Ok(members) =>
408 if members.is_empty() {
409 log_print!("📭 No members in Tech Collective");
410 } else {
411 log_print!("👥 Total members: {}", members.len());
412 log_print!("");
413
414 for (index, member) in members.iter().enumerate() {
415 log_print!(
416 "{}. {}",
417 (index + 1).to_string().bright_blue(),
418 member.to_quantus_ss58().bright_green()
419 );
420 }
421 },
422 Err(e) => {
423 log_verbose!("⚠️ Failed to get member list: {:?}", e);
424 match get_member_count(&quantus_client).await? {
426 Some(count_data) => {
427 log_verbose!("✅ Got member count data: {:?}", count_data);
428 if count_data > 0 {
429 log_print!(
430 "👥 Total members: {} (detailed list unavailable)",
431 count_data
432 );
433 } else {
434 log_print!("📭 No members in Tech Collective");
435 }
436 },
437 None => {
438 log_print!("📭 No member data found - Tech Collective may be empty");
439 },
440 }
441 },
442 }
443
444 log_print!("");
445 log_print!("💡 To check specific membership:");
446 log_print!(" quantus tech-collective is-member --address <ADDRESS>");
447 log_print!("💡 To add a member (requires sudo):");
448 log_print!(
449 " quantus tech-collective add-member --who <ADDRESS> --from <SUDO_WALLET>"
450 );
451 },
452
453 TechCollectiveCommands::IsMember { address } => {
454 log_print!("🔍 Checking Tech Collective membership ");
455
456 let resolved_address = resolve_address(&address)?;
458
459 log_print!(" 👤 Address: {}", resolved_address.bright_cyan());
460
461 if is_member(&quantus_client, &resolved_address).await? {
462 log_success!("✅ Address IS a member of Tech Collective!");
463 log_print!("👥 Member data found in storage");
464 } else {
465 log_print!("❌ Address is NOT a member of Tech Collective");
466 log_print!("💡 No membership record found for this address");
467 }
468 },
469
470 TechCollectiveCommands::CheckSudo { address } => {
471 log_print!("🏛️ Checking sudo permissions ");
472
473 match get_sudo_account(&quantus_client).await? {
474 Some(sudo_account) => {
475 let sudo_address = sudo_account.to_quantus_ss58();
476 log_verbose!("🔍 Found sudo account: {}", sudo_address);
477 log_success!("✅ Found sudo account: {}", sudo_address.bright_green());
478
479 if let Some(check_address) = address {
481 log_verbose!("🔍 Checking if provided address is sudo...");
482
483 let resolved_address = resolve_address(&check_address)?;
485 log_verbose!(" 👤 Address to check: {}", resolved_address);
486
487 if sudo_address == resolved_address {
488 log_success!("✅ Provided address IS the sudo account!");
489 } else {
490 log_print!("❌ Provided address is NOT the sudo account");
491 log_verbose!("💡 Provided address: {}", resolved_address);
492 log_verbose!("💡 Actual sudo address: {}", sudo_address);
493 }
494 } else {
495 log_verbose!("💡 Use 'quantus tech-collective check-sudo --address <ADDRESS>' to check if a specific address is sudo");
497 }
498 },
499 None => {
500 log_print!("📭 No sudo account found in network");
501 log_verbose!("💡 The network may not have sudo configured");
502 },
503 }
504 },
505
506 TechCollectiveCommands::ListReferenda => {
507 log_print!("📜 Active Tech Referenda ");
508 log_print!("");
509
510 log_print!("💡 Referenda listing requires TechReferenda pallet storage queries");
511 log_print!(
512 "💡 Use 'quantus call --pallet TechReferenda --call <method>' for direct interaction"
513 );
514 },
515
516 TechCollectiveCommands::GetReferendum { index } => {
517 log_print!("📄 Tech Referendum #{} Details ", index);
518 log_print!("");
519
520 log_print!("💡 Referendum details require TechReferenda storage access");
521 log_print!("💡 Query ReferendumInfoFor storage with index {}", index);
522 },
523 };
524
525 Ok(())
526}