quantus_cli/cli/
events.rs

1use crate::{
2	chain::client::QuantusClient, cli::address_format::QuantusSS58, log_print, log_verbose,
3};
4use colored::Colorize;
5use jsonrpsee::core::client::ClientT;
6use std::str::FromStr;
7
8pub async fn handle_events_command(
9	block: Option<u32>,
10	block_hash: Option<String>,
11	finalized: bool,
12	pallet_filter: Option<String>,
13	raw: bool,
14	decode: bool,
15	node_url: &str,
16) -> crate::error::Result<()> {
17	// Connect to the chain
18	let quantus_client = QuantusClient::new(node_url).await?;
19
20	// Determine which block to query based on parameters
21	let (block_hash, block_number) = if let Some(block_num) = block {
22		log_print!("📋 Querying events from block #{}", block_num);
23
24		// Get block hash by number using RPC
25		let hash: subxt::utils::H256 = quantus_client
26			.rpc_client()
27			.request::<subxt::utils::H256, [u32; 1]>("chain_getBlockHash", [block_num])
28			.await
29			.map_err(|e| {
30				crate::error::QuantusError::NetworkError(format!(
31					"Failed to get block hash for #{block_num}: {e:?}"
32				))
33			})?;
34		(hash, block_num)
35	} else if let Some(hash_str) = block_hash {
36		log_print!("📋 Querying events from block hash: {}", hash_str);
37
38		// Parse hash string
39		let hash = subxt::utils::H256::from_str(&hash_str).map_err(|e| {
40			crate::error::QuantusError::NetworkError(format!("Invalid block hash: {e}"))
41		})?;
42
43		// Get block number from hash
44		let block_header: serde_json::Value = quantus_client
45			.rpc_client()
46			.request::<serde_json::Value, [String; 1]>("chain_getHeader", [hash_str.clone()])
47			.await
48			.map_err(|e| {
49				crate::error::QuantusError::NetworkError(format!(
50					"Failed to get block header for hash {hash_str}: {e:?}"
51				))
52			})?;
53
54		let block_num = if let Some(block_number_str) = block_header["number"].as_str() {
55			u64::from_str_radix(&block_number_str[2..], 16).map(|n| n as u32).unwrap_or(0)
56		} else {
57			0
58		};
59
60		(hash, block_num)
61	} else if finalized {
62		log_print!("📋 Querying events from finalized block");
63
64		// Get finalized head
65		let hash: subxt::utils::H256 = quantus_client
66			.rpc_client()
67			.request::<subxt::utils::H256, [(); 0]>("chain_getFinalizedHead", [])
68			.await
69			.map_err(|e| {
70				crate::error::QuantusError::NetworkError(format!(
71					"Failed to get finalized head: {e:?}"
72				))
73			})?;
74
75		// Get block number from finalized head
76		let block_header: serde_json::Value = quantus_client
77			.rpc_client()
78			.request::<serde_json::Value, [String; 1]>("chain_getHeader", [format!("0x{hash:x}")])
79			.await
80			.map_err(|e| {
81				crate::error::QuantusError::NetworkError(format!(
82					"Failed to get finalized block header: {e:?}"
83				))
84			})?;
85
86		let block_num = if let Some(block_number_str) = block_header["number"].as_str() {
87			u64::from_str_radix(&block_number_str[2..], 16).map(|n| n as u32).unwrap_or(0)
88		} else {
89			0
90		};
91
92		(hash, block_num)
93	} else {
94		// Use latest block (default)
95		log_print!("📋 Querying events from latest block");
96
97		let hash = quantus_client.get_latest_block().await?;
98
99		// Get block number from latest block
100		let block_header: serde_json::Value = quantus_client
101			.rpc_client()
102			.request::<serde_json::Value, [(); 0]>("chain_getHeader", [])
103			.await
104			.map_err(|e| {
105				crate::error::QuantusError::NetworkError(format!(
106					"Failed to get latest block header: {e:?}"
107				))
108			})?;
109
110		let block_num = if let Some(block_number_str) = block_header["number"].as_str() {
111			u64::from_str_radix(&block_number_str[2..], 16).map(|n| n as u32).unwrap_or(0)
112		} else {
113			0
114		};
115
116		(hash, block_num)
117	};
118
119	log_print!("🔮 Quantus CLI");
120	log_print!("🎯 Found Block #{}", block_number);
121
122	// Get events from the block
123	let events = quantus_client.client().blocks().at(block_hash).await?.events().await?;
124
125	log_print!("📋 Block Events:");
126
127	let mut event_count = 0;
128	let mut filtered_count = 0;
129
130	// Iterate through all events
131	for event in events.iter() {
132		event_count += 1;
133
134		let event = event.map_err(|e| {
135			crate::error::QuantusError::NetworkError(format!("Failed to decode event: {e:?}"))
136		})?;
137
138		// Apply pallet filter if specified
139		if let Some(ref filter) = pallet_filter {
140			if event.pallet_name() != filter {
141				continue;
142			}
143		}
144
145		filtered_count += 1;
146
147		// Display event information
148		log_print!(
149			"  📌 {}.{}",
150			event.pallet_name().bright_cyan(),
151			event.variant_name().bright_yellow()
152		);
153
154		// Enhanced event decoding with details
155		if !raw && decode {
156			decode_event_details(&event)?;
157		}
158
159		// Show raw data if requested or in verbose mode
160		if raw || crate::log::is_verbose() {
161			log_verbose!("     📝 Raw event data: {:?}", event.field_bytes());
162		}
163	}
164
165	// Summary
166	log_print!("");
167	if let Some(ref filter) = pallet_filter {
168		log_print!(
169			"📊 Summary: {} events total, {} events from {} pallet",
170			event_count,
171			filtered_count,
172			filter.bright_cyan()
173		);
174	} else {
175		log_print!("📊 Summary: {} events total", event_count);
176	}
177
178	if filtered_count == 0 && pallet_filter.is_some() {
179		log_print!("💡 Tip: No events found for the specified pallet. Try without --pallet filter to see all events.");
180	}
181
182	log_print!("💡 Tip: Use --verbose for raw event data");
183	log_print!("💡 Tip: Use --pallet <PALLET_NAME> to filter events by pallet");
184
185	Ok(())
186}
187
188/// Decode and display detailed event information using typed events
189fn decode_event_details<T: subxt::Config>(
190	event: &subxt::events::EventDetails<T>,
191) -> crate::error::Result<()> {
192	// Use typed event decoding for all events
193	if let Some(typed_message) = decode_event_typed(event) {
194		log_print!("{}", typed_message);
195	}
196
197	Ok(())
198}
199
200fn decode_event_typed<T: subxt::Config>(event: &subxt::events::EventDetails<T>) -> Option<String> {
201	// Get the typed event using SubXT's generated types
202	let typed_event = event.as_root_event::<crate::chain::quantus_subxt::api::Event>().ok()?;
203
204	// Format the event with improved AccountId32 display
205	let formatted_event = format_event_with_ss58_addresses(&typed_event);
206
207	// GENERIC DISPLAY: Show all events in a consistent, automatic format
208	Some(format!("     📝 {}", formatted_event.bright_cyan()))
209}
210
211/// Format event string with SS58 addresses instead of raw AccountId32 bytes
212fn format_event_with_ss58_addresses(event: &crate::chain::quantus_subxt::api::Event) -> String {
213	let debug_str = format!("{event:?}");
214
215	// Replace all AccountId32 patterns with SS58 addresses
216	let mut result = debug_str.clone();
217
218	// Find and replace all AccountId32 patterns
219	let mut replacements = 0;
220	while let Some(account_id) = extract_account_id_from_debug(&result) {
221		let ss58_address = format_account_id(&account_id);
222		let account_debug = format!("{account_id:?}");
223		result = result.replace(&account_debug, &ss58_address);
224		replacements += 1;
225		if replacements > 10 {
226			break;
227		} // Prevent infinite loop
228	}
229
230	result
231}
232
233/// Extract AccountId32 from debug string format
234fn extract_account_id_from_debug(debug_str: &str) -> Option<subxt::utils::AccountId32> {
235	// Look for AccountId32 pattern in the debug string
236	if let Some(start) = debug_str.find("AccountId32([") {
237		if let Some(end) = debug_str[start..].find("])") {
238			let bytes_str = &debug_str[start + 13..start + end]; // "AccountId32([" has 13 chars
239
240			// Parse the bytes array
241			let bytes: Vec<u8> = bytes_str
242				.split(',')
243				.map(|s| s.trim().parse::<u8>().ok())
244				.collect::<Option<Vec<u8>>>()?;
245
246			if bytes.len() == 32 {
247				let mut account_bytes = [0u8; 32];
248				account_bytes.copy_from_slice(&bytes);
249				return Some(subxt::utils::AccountId32::from(account_bytes));
250			}
251		}
252	}
253	None
254}
255
256/// Convert AccountId32 to SS58 address for better readability
257fn format_account_id(account_id: &subxt::utils::AccountId32) -> String {
258	account_id.to_quantus_ss58()
259}