quantus_cli/cli/
block.rs

1//! `quantus block` subcommand - detailed block analysis
2use crate::{
3	chain::client::QuantusClient,
4	cli::{address_format::QuantusSS58, storage},
5	error::QuantusError,
6	log_error, log_print, log_success,
7};
8use clap::Subcommand;
9use colored::Colorize;
10use qp_poseidon::PoseidonHasher;
11use sp_core::crypto::Ss58Codec;
12use std::str::FromStr;
13use subxt::events::EventDetails;
14
15/// Block management and analysis commands
16#[derive(Subcommand, Debug)]
17pub enum BlockCommands {
18	/// Detailed block analysis
19	Analyze {
20		/// Block number to analyze
21		#[arg(long)]
22		number: Option<u32>,
23
24		/// Block hash to analyze (alternative to number)
25		#[arg(long)]
26		hash: Option<String>,
27
28		/// Use latest block if no number/hash specified
29		#[arg(long)]
30		latest: bool,
31
32		/// Show storage statistics for this block
33		#[arg(long)]
34		storage: bool,
35
36		/// Show detailed extrinsic information
37		#[arg(long)]
38		extrinsics: bool,
39
40		/// Show detailed information for ALL extrinsics (not just first 3)
41		#[arg(long)]
42		extrinsics_details: bool,
43
44		/// Show events from this block
45		#[arg(long)]
46		events: bool,
47
48		/// Show all available information
49		#[arg(long)]
50		all: bool,
51	},
52
53	/// List blocks in range with summary info
54	List {
55		/// Start block number
56		#[arg(long)]
57		start: u32,
58		/// End block number
59		#[arg(long)]
60		end: u32,
61		/// Block step (default: 1)
62		#[arg(long)]
63		step: Option<u32>,
64	},
65}
66
67/// Handle block commands
68pub async fn handle_block_command(
69	command: BlockCommands,
70	node_url: &str,
71) -> crate::error::Result<()> {
72	match command {
73		BlockCommands::Analyze {
74			number,
75			hash,
76			latest,
77			storage,
78			extrinsics,
79			extrinsics_details,
80			events,
81			all,
82		} =>
83			handle_block_analyze_command(
84				number,
85				hash,
86				latest,
87				storage,
88				extrinsics,
89				extrinsics_details,
90				events,
91				all,
92				node_url,
93			)
94			.await,
95		BlockCommands::List { start, end, step } =>
96			handle_block_list_command(start, end, step, node_url).await,
97	}
98}
99
100/// Handle detailed block analysis
101async fn handle_block_analyze_command(
102	number: Option<u32>,
103	hash: Option<String>,
104	latest: bool,
105	storage: bool,
106	extrinsics: bool,
107	extrinsics_details: bool,
108	events: bool,
109	all: bool,
110	node_url: &str,
111) -> crate::error::Result<()> {
112	log_print!("🔍 Block Analysis");
113
114	let quantus_client = QuantusClient::new(node_url).await?;
115
116	// Determine which block to analyze
117	let (block_number, block_hash) = if let Some(num) = number {
118		// Convert number to hash using our storage function
119		let hash = storage::resolve_block_hash(&quantus_client, &num.to_string()).await?;
120		(num, hash)
121	} else if let Some(h) = hash {
122		// Parse hash and get block number from storage
123		let parsed_hash = storage::resolve_block_hash(&quantus_client, &h).await?;
124		// Get block number by querying System::Number at that block
125		let storage_at = quantus_client.client().storage().at(parsed_hash);
126		let number_addr = crate::chain::quantus_subxt::api::storage().system().number();
127		let block_num = storage_at.fetch_or_default(&number_addr).await.map_err(|e| {
128			QuantusError::NetworkError(format!("Failed to get block number: {e:?}"))
129		})?;
130		(block_num, parsed_hash)
131	} else if latest {
132		// Use latest block
133		let hash = quantus_client.get_latest_block().await?;
134		let storage_at = quantus_client.client().storage().at(hash);
135		let number_addr = crate::chain::quantus_subxt::api::storage().system().number();
136		let block_num = storage_at.fetch_or_default(&number_addr).await.map_err(|e| {
137			QuantusError::NetworkError(format!("Failed to get latest block number: {e:?}"))
138		})?;
139		(block_num, hash)
140	} else {
141		return Err(QuantusError::Generic("Must specify --number, --hash, or --latest".to_string()));
142	};
143
144	log_print!("📦 Block #{} - {:#x}", block_number, block_hash);
145	log_print!("");
146
147	// Get basic block information using RPC
148	use jsonrpsee::core::client::ClientT;
149	let block_data: serde_json::Value = quantus_client
150		.rpc_client()
151		.request("chain_getBlock", [format!("{block_hash:#x}")])
152		.await
153		.map_err(|e| QuantusError::NetworkError(format!("Failed to get block data: {e:?}")))?;
154
155	// Show basic block header information
156	show_block_header(&block_data)?;
157
158	// Show storage statistics if requested or --all
159	if storage || all {
160		show_storage_statistics(&quantus_client, block_hash).await?;
161	}
162
163	// Show events if requested or --all
164	if events || all {
165		show_block_events(block_number, node_url).await?;
166	}
167
168	// Show extrinsic details if requested or --all
169	if extrinsics || all {
170		show_extrinsic_details(&quantus_client, block_hash, &block_data).await?;
171	}
172
173	// Show detailed information for ALL extrinsics if requested (only when explicitly requested)
174	if extrinsics_details {
175		show_all_extrinsic_details(&quantus_client, block_hash, &block_data).await?;
176	}
177
178	log_success!("✅ Block analysis complete!");
179	log_print!("💡 Use --all to see all information, or combine --storage --events --extrinsics --extrinsics-details");
180
181	Ok(())
182}
183
184/// Show block header information
185fn show_block_header(block_data: &serde_json::Value) -> crate::error::Result<()> {
186	if let Some(block) = block_data.get("block") {
187		if let Some(header) = block.get("header") {
188			log_print!("📋 Block Header:");
189			if let Some(parent_hash) = header.get("parentHash") {
190				log_print!(
191					"   • Parent Hash: {}",
192					parent_hash.as_str().unwrap_or("unknown").bright_blue()
193				);
194			}
195			if let Some(state_root) = header.get("stateRoot") {
196				log_print!(
197					"   • State Root: {}",
198					state_root.as_str().unwrap_or("unknown").bright_green()
199				);
200			}
201			if let Some(extrinsics_root) = header.get("extrinsicsRoot") {
202				log_print!(
203					"   • Extrinsics Root: {}",
204					extrinsics_root.as_str().unwrap_or("unknown").bright_yellow()
205				);
206			}
207
208			// Try to get timestamp from header if available
209			if let Some(timestamp) = header.get("timestamp") {
210				if let Some(timestamp_num) = timestamp.as_u64() {
211					// Convert milliseconds to human readable time
212					let timestamp_secs = timestamp_num / 1000;
213					let datetime = chrono::DateTime::from_timestamp(timestamp_secs as i64, 0);
214					if let Some(dt) = datetime {
215						log_print!(
216							"   • Timestamp: {} ({})",
217							dt.format("%Y-%m-%d %H:%M:%S UTC").to_string().bright_cyan(),
218							timestamp_num.to_string().bright_yellow()
219						);
220					}
221				}
222			}
223		}
224
225		if let Some(extrinsics) = block.get("extrinsics") {
226			if let Some(extrinsics_array) = extrinsics.as_array() {
227				log_print!(
228					"   • Extrinsics Count: {}",
229					extrinsics_array.len().to_string().bright_magenta()
230				);
231
232				// Calculate approximate block size
233				let block_size = serde_json::to_string(block_data).unwrap_or_default().len();
234				log_print!("   • Approximate Size: {} bytes", block_size.to_string().bright_cyan());
235			}
236		}
237	}
238
239	log_print!("");
240	Ok(())
241}
242
243/// Show storage statistics for the block
244async fn show_storage_statistics(
245	quantus_client: &QuantusClient,
246	block_hash: subxt::utils::H256,
247) -> crate::error::Result<()> {
248	log_print!("💾 Storage Statistics:");
249
250	// Account count
251	let account_count =
252		storage::count_storage_entries(quantus_client, "System", "Account", block_hash).await?;
253	log_print!("   • Accounts: {}", account_count.to_string().bright_green());
254
255	// BlockHash count
256	let blockhash_count =
257		storage::count_storage_entries(quantus_client, "System", "BlockHash", block_hash).await?;
258	log_print!("   • Block Hashes: {}", blockhash_count.to_string().bright_blue());
259
260	// Event count
261	let storage_at = quantus_client.client().storage().at(block_hash);
262	let event_count_addr = crate::chain::quantus_subxt::api::storage().system().event_count();
263	let event_count = storage_at
264		.fetch_or_default(&event_count_addr)
265		.await
266		.map_err(|e| {
267			log_error!("Failed to get event count: {:?}", e);
268			e
269		})
270		.unwrap_or_default();
271	log_print!("   • Events: {}", event_count.to_string().bright_yellow());
272
273	// Try to get timestamp from Timestamp::Now storage
274	let timestamp_addr = crate::chain::quantus_subxt::api::storage().timestamp().now();
275	match storage_at.fetch(&timestamp_addr).await {
276		Ok(Some(timestamp)) => {
277			// Convert milliseconds to human readable time
278			let timestamp_secs = timestamp / 1000;
279			let datetime = chrono::DateTime::from_timestamp(timestamp_secs as i64, 0);
280			if let Some(dt) = datetime {
281				log_print!(
282					"   • Block Time: {} ({})",
283					dt.format("%Y-%m-%d %H:%M:%S UTC").to_string().bright_green(),
284					timestamp.to_string().bright_yellow()
285				);
286			}
287		},
288		Ok(None) => {
289			log_print!("   • Block Time: {}", "no timestamp".bright_yellow());
290		},
291		Err(_) => {
292			log_print!("   • Block Time: {}", "unknown".bright_yellow());
293		},
294	}
295
296	log_print!("");
297	Ok(())
298}
299
300/// Show events from the block
301async fn show_block_events(block_number: u32, node_url: &str) -> crate::error::Result<()> {
302	log_print!("📋 Events:");
303
304	// Use the existing events command internally
305	match crate::cli::events::handle_events_command(
306		Some(block_number),
307		None,
308		false,
309		None,
310		false,
311		true,
312		node_url,
313	)
314	.await
315	{
316		Ok(_) => {
317			// Events were already printed by the events command
318		},
319		Err(e) => log_error!("Failed to get events: {}", e),
320	}
321
322	log_print!("");
323	Ok(())
324}
325
326/// Show extrinsic details
327async fn show_extrinsic_details(
328	quantus_client: &QuantusClient,
329	block_hash: subxt::utils::H256,
330	block_data: &serde_json::Value,
331) -> crate::error::Result<()> {
332	if let Some(block) = block_data.get("block") {
333		if let Some(extrinsics) = block.get("extrinsics") {
334			if let Some(extrinsics_array) = extrinsics.as_array() {
335				log_print!("🔧 Extrinsics Details:");
336				log_print!(
337					"   • Total Count: {}",
338					extrinsics_array.len().to_string().bright_green()
339				);
340
341				// Calculate total size of all extrinsics in actual bytes
342				let mut total_size_bytes = 0;
343				let mut total_size_chars = 0;
344				for extrinsic in extrinsics_array.iter() {
345					if let Some(ext_str) = extrinsic.as_str() {
346						total_size_chars += ext_str.len();
347						// Convert hex string to actual bytes
348						if let Some(hex_part) = ext_str.strip_prefix("0x") {
349							// Remove "0x" prefix and convert hex to bytes
350							if hex_part.len() % 2 == 0 {
351								total_size_bytes += hex_part.len() / 2;
352							} else {
353								total_size_bytes += hex_part.len().div_ceil(2);
354							}
355						} else {
356							// If not hex, assume it's already in bytes
357							total_size_bytes += ext_str.len();
358						}
359					}
360				}
361				log_print!(
362					"   • Total Size: {:.1} KB ({} chars)",
363					total_size_bytes as f64 / 1024.0,
364					total_size_chars.to_string().bright_cyan()
365				);
366
367				// Pre-fetch events and group by extrinsic index
368				let events =
369					quantus_client.client().blocks().at(block_hash).await?.events().await?;
370				let mut events_by_ex_idx: std::collections::BTreeMap<usize, Vec<String>> =
371					std::collections::BTreeMap::new();
372				for ev in events.iter().flatten() {
373					if let subxt::events::Phase::ApplyExtrinsic(ex_idx) = ev.phase() {
374						let msg = format_event_details(&ev);
375						events_by_ex_idx.entry(ex_idx as usize).or_default().push(msg);
376					}
377				}
378
379				// Get extrinsics via subxt for detailed analysis
380				let block = quantus_client.client().blocks().at(block_hash).await?;
381				let extrinsics = block.extrinsics().await?;
382
383				// Get parent block hash for nonce calculation
384				let parent_hash = {
385					use jsonrpsee::core::client::ClientT;
386					let header: serde_json::Value = quantus_client
387						.rpc_client()
388						.request("chain_getHeader", [format!("{block_hash:#x}")])
389						.await
390						.map_err(|e| {
391							crate::error::QuantusError::NetworkError(format!(
392								"Failed to get header: {e:?}"
393							))
394						})?;
395					let parent_hash_str = header["parentHash"].as_str().ok_or_else(|| {
396						crate::error::QuantusError::NetworkError(
397							"Missing parentHash in header".to_string(),
398						)
399					})?;
400					subxt::utils::H256::from_str(parent_hash_str).map_err(|e| {
401						crate::error::QuantusError::NetworkError(format!(
402							"Invalid parent hash: {e:?}"
403						))
404					})?
405				};
406
407				// Track nonce per signer in this block
408				let mut signer_nonce_tracker: std::collections::HashMap<String, u32> =
409					std::collections::HashMap::new();
410
411				// Show first 3 extrinsics with details
412				for (index, extrinsic) in extrinsics_array.iter().take(3).enumerate() {
413					let ext_str = extrinsic.as_str().unwrap_or("unknown");
414					let (ext_size_bytes, preview, hash_hex) = summarize_extrinsic(ext_str);
415					log_print!(
416						"   {}. Hash: {} | Size: {} bytes | Data: {}...",
417						(index + 1).to_string().bright_yellow(),
418						hash_hex.bright_blue(),
419						ext_size_bytes.to_string().bright_cyan(),
420						preview.bright_magenta()
421					);
422
423					// Extract signer and transaction details
424					if let Some(ext_details) = extrinsics.iter().nth(index) {
425						if ext_details.is_signed() {
426							// Extract signer and tip from events
427							let mut signer_from_events: Option<String> = None;
428							let mut tip_from_events: Option<u128> = None;
429
430							if let Some(event_list) = events_by_ex_idx.get(&index) {
431								for event_line in event_list {
432									if event_line.contains("TransactionFeePaid") {
433										// Extract signer (who field)
434										if let Some(who_start) = event_line.find("who: ") {
435											if let Some(who_end) =
436												event_line[who_start + 5..].find(',')
437											{
438												let signer = &event_line
439													[who_start + 5..who_start + 5 + who_end];
440												signer_from_events = Some(signer.to_string());
441											}
442										}
443										// Extract tip
444										if let Some(tip_start) = event_line.find("tip: ") {
445											if let Some(tip_end) =
446												event_line[tip_start + 5..].find(' ')
447											{
448												let tip_str = &event_line
449													[tip_start + 5..tip_start + 5 + tip_end];
450												if let Ok(tip_val) = tip_str.parse::<u128>() {
451													tip_from_events = Some(tip_val);
452												}
453											}
454										}
455									}
456								}
457							}
458
459							if let Some(ref signer) = signer_from_events {
460								log_print!("       • Signer: {}", signer);
461
462								// Calculate nonce from parent block + track per signer
463								if !signer_nonce_tracker.contains_key(signer) {
464									let nonce = get_account_nonce_at_block(
465										quantus_client,
466										signer,
467										parent_hash,
468									)
469									.await
470									.unwrap_or(0);
471									signer_nonce_tracker.insert(signer.clone(), nonce);
472								}
473
474								// Get current nonce for this signer (increments for each tx from
475								// same signer)
476								let current_nonce = *signer_nonce_tracker.get(signer).unwrap();
477								log_print!("       • Nonce: {}", current_nonce);
478
479								// Increment for next transaction from this signer
480								signer_nonce_tracker.insert(signer.clone(), current_nonce + 1);
481							}
482
483							if let Some(tip) = tip_from_events {
484								// Format tip nicely
485								let tip_hei = tip as f64 / 1_000_000_000_000.0;
486								log_print!("       • Tip: {:.6} HEI", tip_hei);
487							}
488						} else {
489							log_print!("       • Unsigned extrinsic");
490						}
491					}
492
493					// Related events (if any)
494					if let Some(list) = events_by_ex_idx.get(&index) {
495						for line in list {
496							log_print!("       ↪ {}", line);
497						}
498					}
499				}
500
501				if extrinsics_array.len() > 3 {
502					log_print!(
503						"   ... and {} more extrinsics",
504						(extrinsics_array.len() - 3).to_string().bright_blue()
505					);
506				}
507			}
508		}
509	}
510	log_print!("");
511	Ok(())
512}
513
514/// Show detailed information for ALL extrinsics
515async fn show_all_extrinsic_details(
516	quantus_client: &QuantusClient,
517	block_hash: subxt::utils::H256,
518	block_data: &serde_json::Value,
519) -> crate::error::Result<()> {
520	if let Some(block) = block_data.get("block") {
521		if let Some(extrinsics) = block.get("extrinsics") {
522			if let Some(extrinsics_array) = extrinsics.as_array() {
523				log_print!("🔧 ALL Extrinsics Details:");
524				log_print!(
525					"   • Total Count: {}",
526					extrinsics_array.len().to_string().bright_green()
527				);
528
529				// Calculate total size of all extrinsics in actual bytes
530				let mut total_size_bytes = 0;
531				let mut total_size_chars = 0;
532				for extrinsic in extrinsics_array.iter() {
533					if let Some(ext_str) = extrinsic.as_str() {
534						total_size_chars += ext_str.len();
535						// Convert hex string to actual bytes
536						if let Some(hex_part) = ext_str.strip_prefix("0x") {
537							// Remove "0x" prefix and convert hex to bytes
538							if hex_part.len() % 2 == 0 {
539								total_size_bytes += hex_part.len() / 2;
540							} else {
541								total_size_bytes += hex_part.len().div_ceil(2);
542							}
543						} else {
544							// If not hex, assume it's already in bytes
545							total_size_bytes += ext_str.len();
546						}
547					}
548				}
549				log_print!(
550					"   • Total Size: {:.1} KB ({} chars)",
551					total_size_bytes as f64 / 1024.0,
552					total_size_chars.to_string().bright_cyan()
553				);
554
555				// Pre-fetch events and group by extrinsic index
556				let events =
557					quantus_client.client().blocks().at(block_hash).await?.events().await?;
558				let mut events_by_ex_idx: std::collections::BTreeMap<usize, Vec<String>> =
559					std::collections::BTreeMap::new();
560				for ev in events.iter().flatten() {
561					if let subxt::events::Phase::ApplyExtrinsic(ex_idx) = ev.phase() {
562						let msg = format_event_details(&ev);
563						events_by_ex_idx.entry(ex_idx as usize).or_default().push(msg);
564					}
565				}
566
567				// Get extrinsics via subxt for detailed analysis
568				let block = quantus_client.client().blocks().at(block_hash).await?;
569				let extrinsics = block.extrinsics().await?;
570
571				// Get parent block hash for nonce calculation
572				let parent_hash = {
573					use jsonrpsee::core::client::ClientT;
574					let header: serde_json::Value = quantus_client
575						.rpc_client()
576						.request("chain_getHeader", [format!("{block_hash:#x}")])
577						.await
578						.map_err(|e| {
579							crate::error::QuantusError::NetworkError(format!(
580								"Failed to get header: {e:?}"
581							))
582						})?;
583					let parent_hash_str = header["parentHash"].as_str().ok_or_else(|| {
584						crate::error::QuantusError::NetworkError(
585							"Missing parentHash in header".to_string(),
586						)
587					})?;
588					subxt::utils::H256::from_str(parent_hash_str).map_err(|e| {
589						crate::error::QuantusError::NetworkError(format!(
590							"Invalid parent hash: {e:?}"
591						))
592					})?
593				};
594
595				// Track nonce per signer in this block
596				let mut signer_nonce_tracker: std::collections::HashMap<String, u32> =
597					std::collections::HashMap::new();
598
599				// Show all extrinsics with details
600				for (index, extrinsic) in extrinsics_array.iter().enumerate() {
601					let ext_str = extrinsic.as_str().unwrap_or("unknown");
602					let (ext_size_bytes, preview, hash_hex) = summarize_extrinsic(ext_str);
603					log_print!(
604						"   {}. Hash: {} | Size: {} bytes | Data: {}...",
605						(index + 1).to_string().bright_yellow(),
606						hash_hex.bright_blue(),
607						ext_size_bytes.to_string().bright_cyan(),
608						preview.bright_magenta()
609					);
610
611					// Extract signer and transaction details
612					if let Some(ext_details) = extrinsics.iter().nth(index) {
613						if ext_details.is_signed() {
614							// Extract signer and tip from events
615							let mut signer_from_events: Option<String> = None;
616							let mut tip_from_events: Option<u128> = None;
617
618							if let Some(event_list) = events_by_ex_idx.get(&index) {
619								for event_line in event_list {
620									if event_line.contains("TransactionFeePaid") {
621										// Extract signer (who field)
622										if let Some(who_start) = event_line.find("who: ") {
623											if let Some(who_end) =
624												event_line[who_start + 5..].find(',')
625											{
626												let signer = &event_line
627													[who_start + 5..who_start + 5 + who_end];
628												signer_from_events = Some(signer.to_string());
629											}
630										}
631										// Extract tip
632										if let Some(tip_start) = event_line.find("tip: ") {
633											if let Some(tip_end) =
634												event_line[tip_start + 5..].find(' ')
635											{
636												let tip_str = &event_line
637													[tip_start + 5..tip_start + 5 + tip_end];
638												if let Ok(tip_val) = tip_str.parse::<u128>() {
639													tip_from_events = Some(tip_val);
640												}
641											}
642										}
643									}
644								}
645							}
646
647							if let Some(ref signer) = signer_from_events {
648								log_print!("       • Signer: {}", signer);
649
650								// Calculate nonce from parent block + track per signer
651								if !signer_nonce_tracker.contains_key(signer) {
652									let nonce = get_account_nonce_at_block(
653										quantus_client,
654										signer,
655										parent_hash,
656									)
657									.await
658									.unwrap_or(0);
659									signer_nonce_tracker.insert(signer.clone(), nonce);
660								}
661
662								// Get current nonce for this signer (increments for each tx from
663								// same signer)
664								let current_nonce = *signer_nonce_tracker.get(signer).unwrap();
665								log_print!("       • Nonce: {}", current_nonce);
666
667								// Increment for next transaction from this signer
668								signer_nonce_tracker.insert(signer.clone(), current_nonce + 1);
669							}
670
671							if let Some(tip) = tip_from_events {
672								// Format tip nicely
673								let tip_hei = tip as f64 / 1_000_000_000_000.0;
674								log_print!("       • Tip: {:.6} HEI", tip_hei);
675							}
676						} else {
677							log_print!("       • Unsigned extrinsic");
678						}
679					}
680
681					// Related events (if any)
682					if let Some(list) = events_by_ex_idx.get(&index) {
683						for line in list {
684							log_print!("       ↪ {}", line);
685						}
686					}
687				}
688			}
689		}
690	}
691	log_print!("");
692	Ok(())
693}
694
695/// Compute summary info for an extrinsic hex string: (size_bytes, preview, hash_hex)
696fn summarize_extrinsic(ext_hex: &str) -> (usize, String, String) {
697	let ext_str = ext_hex;
698	let (bytes, size_bytes) = if let Some(hex_part) = ext_str.strip_prefix("0x") {
699		if let Ok(decoded) = hex::decode(hex_part) {
700			let size = decoded.len();
701			(decoded, size)
702		} else {
703			(ext_str.as_bytes().to_vec(), ext_str.len())
704		}
705	} else {
706		(ext_str.as_bytes().to_vec(), ext_str.len())
707	};
708
709	// Compute extrinsic hash using Poseidon (chain hasher)
710	let h = <PoseidonHasher as sp_runtime::traits::Hash>::hash(&bytes);
711	let hash_hex = format!("{h:#x}");
712
713	let preview = if ext_str.len() > 20 { ext_str[..20].to_string() } else { ext_str.to_string() };
714	(size_bytes, preview, hash_hex)
715}
716
717/// Format event details with typed decoding and nicer AccountId formatting
718fn format_event_details<T: subxt::Config>(event: &EventDetails<T>) -> String {
719	if let Ok(typed) = event.as_root_event::<crate::chain::quantus_subxt::api::Event>() {
720		let formatted = format_event_with_ss58_addresses(&typed);
721		return format!("📝 {}.{} {}", event.pallet_name(), event.variant_name(), formatted);
722	}
723	format!("📝 {}.{}", event.pallet_name(), event.variant_name())
724}
725
726fn format_event_with_ss58_addresses(event: &crate::chain::quantus_subxt::api::Event) -> String {
727	let debug_str = format!("{event:?}");
728	let mut result = debug_str.clone();
729	let mut attempts = 0;
730	while let Some(account_id) = extract_account_id_from_debug(&result) {
731		let ss58_address = format_account_id(&account_id);
732		let account_debug = format!("{account_id:?}");
733		result = result.replace(&account_debug, &ss58_address);
734		attempts += 1;
735		if attempts > 10 {
736			break;
737		}
738	}
739	result
740}
741
742fn extract_account_id_from_debug(debug_str: &str) -> Option<subxt::utils::AccountId32> {
743	if let Some(start) = debug_str.find("AccountId32([") {
744		// "
745		if let Some(end) = debug_str[start..].find("])") {
746			let bytes_str = &debug_str[start + 13..start + end];
747			let bytes: Vec<u8> = bytes_str
748				.split(',')
749				.map(|s| s.trim().parse::<u8>().ok())
750				.collect::<Option<Vec<u8>>>()?;
751			if bytes.len() == 32 {
752				let mut account_bytes = [0u8; 32];
753				account_bytes.copy_from_slice(&bytes);
754				return Some(subxt::utils::AccountId32::from(account_bytes));
755			}
756		}
757	}
758	None
759}
760
761fn format_account_id(account_id: &subxt::utils::AccountId32) -> String {
762	account_id.to_quantus_ss58()
763}
764
765/// Get account nonce at specific block
766async fn get_account_nonce_at_block(
767	quantus_client: &QuantusClient,
768	account_address: &str,
769	block_hash: subxt::utils::H256,
770) -> crate::error::Result<u32> {
771	// Parse the SS58 address to AccountId32 (sp-core)
772	let (account_id_sp, _) =
773		sp_core::crypto::AccountId32::from_ss58check_with_version(account_address)
774			.map_err(|e| QuantusError::NetworkError(format!("Invalid SS58 address: {e:?}")))?;
775
776	// Convert to subxt_core AccountId32 for storage query
777	let account_bytes: [u8; 32] = *account_id_sp.as_ref();
778	let account_id = subxt::ext::subxt_core::utils::AccountId32::from(account_bytes);
779
780	// Use SubXT to query System::Account storage at specific block
781	use crate::chain::quantus_subxt::api;
782	let storage_addr = api::storage().system().account(account_id);
783	let storage_at = quantus_client.client().storage().at(block_hash);
784
785	let account_info = storage_at
786		.fetch_or_default(&storage_addr)
787		.await
788		.map_err(|e| QuantusError::NetworkError(format!("Failed to fetch account info: {e:?}")))?;
789
790	Ok(account_info.nonce)
791}
792
793/// Handle block list command
794pub async fn handle_block_list_command(
795	start: u32,
796	end: u32,
797	step: Option<u32>,
798	node_url: &str,
799) -> crate::error::Result<()> {
800	log_print!(
801		"📦 Listing blocks from {} to {}",
802		start.to_string().bright_green(),
803		end.to_string().bright_green()
804	);
805
806	let step = step.unwrap_or(1);
807	if step > 1 {
808		log_print!("📏 Step: {}", step.to_string().bright_cyan());
809	}
810
811	let quantus_client = QuantusClient::new(node_url).await?;
812	list_blocks_in_range(&quantus_client, start, end, step).await
813}
814
815/// List blocks in range with summary information
816async fn list_blocks_in_range(
817	quantus_client: &QuantusClient,
818	start: u32,
819	end: u32,
820	step: u32,
821) -> crate::error::Result<()> {
822	use jsonrpsee::core::client::ClientT;
823
824	log_print!("🔍 Fetching block information...");
825
826	let mut block_count = 0;
827	let mut total_extrinsics = 0;
828	let mut total_events = 0;
829	let mut total_size = 0;
830	let mut tps_values = Vec::new();
831	let mut previous_timestamp: Option<u64> = None;
832
833	// Progress indicator
834	log_print!("📊 Processing {} blocks...", ((end - start) / step + 1).to_string().bright_cyan());
835
836	// Print table header
837	log_print!("");
838	log_print!(
839		"{:<20} {:<20} {:<12} {:<10} {:<8} {:<8}",
840		"Block".bright_green().bold(),
841		"Time".bright_cyan().bold(),
842		"Extrinsics".bright_blue().bold(),
843		"Events".bright_yellow().bold(),
844		"Size".bright_magenta().bold(),
845		"TPS".bright_red().bold()
846	);
847	log_print!(
848		"{:<20} {:<20} {:<12} {:<10} {:<8} {:<8}",
849		"────────────────────".bright_green(),
850		"────────────────────".bright_cyan(),
851		"────────────".bright_blue(),
852		"──────────".bright_yellow(),
853		"──────".bright_magenta(),
854		"──────".bright_red()
855	);
856
857	for block_num in (start..=end).step_by(step as usize) {
858		// Get block hash for this block number
859		let block_hash: subxt::utils::H256 = quantus_client
860			.rpc_client()
861			.request::<subxt::utils::H256, [u32; 1]>("chain_getBlockHash", [block_num])
862			.await
863			.map_err(|e| {
864				QuantusError::NetworkError(format!(
865					"Failed to get block hash for block {block_num}: {e:?}"
866				))
867			})?;
868
869		// Get block data
870		let block_data: serde_json::Value = quantus_client
871			.rpc_client()
872			.request::<serde_json::Value, [String; 1]>(
873				"chain_getBlock",
874				[format!("{block_hash:#x}")],
875			)
876			.await
877			.map_err(|e| {
878				QuantusError::NetworkError(format!(
879					"Failed to get block data for block {block_num}: {e:?}"
880				))
881			})?;
882
883		// Extract basic info
884		let extrinsics_count = if let Some(block) = block_data.get("block") {
885			if let Some(extrinsics) = block.get("extrinsics") {
886				if let Some(extrinsics_array) = extrinsics.as_array() {
887					extrinsics_array.len()
888				} else {
889					0
890				}
891			} else {
892				0
893			}
894		} else {
895			0
896		};
897
898		// Get timestamp from storage
899		let storage_at = quantus_client.client().storage().at(block_hash);
900		let timestamp_addr = crate::chain::quantus_subxt::api::storage().timestamp().now();
901		let timestamp = storage_at.fetch(&timestamp_addr).await.ok().flatten();
902
903		// Get event count
904		let event_count_addr = crate::chain::quantus_subxt::api::storage().system().event_count();
905		let event_count = storage_at.fetch(&event_count_addr).await.ok().flatten().unwrap_or(0);
906
907		// Calculate block size in KB based on actual data
908		let block_size_bytes = if let Some(block) = block_data.get("block") {
909			if let Some(extrinsics) = block.get("extrinsics") {
910				if let Some(extrinsics_array) = extrinsics.as_array() {
911					// Calculate size from actual extrinsic data
912					let mut total_bytes = 0;
913					for extrinsic in extrinsics_array.iter() {
914						if let Some(ext_str) = extrinsic.as_str() {
915							// Remove "0x" prefix and convert hex to bytes
916							if let Some(hex_part) = ext_str.strip_prefix("0x") {
917								if hex_part.len() % 2 == 0 {
918									total_bytes += hex_part.len() / 2;
919								} else {
920									total_bytes += hex_part.len().div_ceil(2);
921								}
922							} else {
923								total_bytes += ext_str.len();
924							}
925						}
926					}
927					// Add some overhead for block header (approximate)
928					total_bytes + 1024 // ~1KB for header
929				} else {
930					0
931				}
932			} else {
933				0
934			}
935		} else {
936			0
937		};
938		let block_size_kb = block_size_bytes as f64 / 1024.0;
939
940		// Update totals
941		block_count += 1;
942		total_extrinsics += extrinsics_count;
943		total_events += event_count;
944		total_size += block_size_bytes;
945
946		// Calculate TPS (Transactions Per Second)
947		let tps_str = match (timestamp, previous_timestamp) {
948			(Some(ts), Some(prev_ts)) => {
949				let time_diff_ms = ts.saturating_sub(prev_ts);
950				if time_diff_ms > 0 {
951					let time_diff_secs = time_diff_ms as f64 / 1000.0;
952					let tps = extrinsics_count as f64 / time_diff_secs;
953					tps_values.push(tps);
954					format!("{tps:.1}")
955				} else {
956					"N/A".to_string()
957				}
958			},
959			_ => "N/A".to_string(),
960		};
961
962		// Display block info - always show full date
963		let time_str = if let Some(ts) = timestamp {
964			let timestamp_secs = ts / 1000;
965			let datetime = chrono::DateTime::from_timestamp(timestamp_secs as i64, 0);
966			if let Some(dt) = datetime {
967				dt.format("%Y-%m-%d %H:%M:%S").to_string()
968			} else {
969				"unknown".to_string()
970			}
971		} else {
972			"unknown".to_string()
973		};
974
975		log_print!(
976			"📦 {:<18} {:<20} {:<12} {:<10} {:<8} {:<8}",
977			format!("#{block_num}").bright_green(),
978			time_str.bright_cyan(),
979			extrinsics_count.to_string().bright_blue(),
980			event_count.to_string().bright_yellow(),
981			format!("{block_size_kb:.1}K").bright_magenta(),
982			tps_str.bright_red()
983		);
984
985		// Update previous timestamp for next iteration
986		previous_timestamp = timestamp.or(previous_timestamp);
987	}
988
989	// Summary
990	log_print!("");
991	log_print!("📊 Summary:");
992	log_print!("   • Blocks processed: {}", block_count.to_string().bright_green());
993	log_print!("   • Total extrinsics: {}", total_extrinsics.to_string().bright_blue());
994	log_print!("   • Total events: {}", total_events.to_string().bright_yellow());
995	log_print!(
996		"   • Total size: {} KB",
997		format!("{:.1}", total_size as f64 / 1024.0).bright_magenta()
998	);
999	log_print!(
1000		"   • Average extrinsics per block: {}",
1001		format!("{:.1}", total_extrinsics as f64 / block_count as f64).bright_cyan()
1002	);
1003	log_print!(
1004		"   • Average events per block: {}",
1005		format!("{:.1}", total_events as f64 / block_count as f64).bright_cyan()
1006	);
1007
1008	// TPS Statistics
1009	if !tps_values.is_empty() {
1010		let max_tps = tps_values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1011		let min_tps = tps_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
1012		let avg_tps = tps_values.iter().sum::<f64>() / tps_values.len() as f64;
1013
1014		log_print!("");
1015		log_print!("🚀 TPS Statistics:");
1016		log_print!("   • MAX TPS: {}", format!("{max_tps:.1}").bright_green().bold());
1017		log_print!("   • MIN TPS: {}", format!("{min_tps:.1}").bright_yellow().bold());
1018		log_print!("   • AVG TPS: {}", format!("{avg_tps:.1}").bright_cyan().bold());
1019		log_print!("   • TPS samples: {}", tps_values.len().to_string().bright_magenta());
1020	} else {
1021		log_print!("");
1022		log_print!(
1023			"🚀 TPS Statistics: No TPS data available (need at least 2 blocks with timestamps)"
1024		);
1025	}
1026
1027	Ok(())
1028}