quantus_cli/cli/
storage.rs

1//! `quantus storage` subcommand - storage operations
2
3use crate::{
4	chain::{client::ChainConfig, quantus_subxt},
5	cli::{address_format::QuantusSS58, progress_spinner::wait_for_tx_confirmation},
6	error::QuantusError,
7	log_error, log_print, log_success, log_verbose,
8};
9use clap::Subcommand;
10use codec::{Decode, Encode};
11use colored::Colorize;
12use serde::Deserialize;
13use sp_core::{
14	crypto::{AccountId32, Ss58Codec},
15	twox_128,
16};
17use std::{collections::BTreeMap, str::FromStr};
18use subxt::OnlineClient;
19
20/// The `AccountData` struct, used in `AccountInfo`.
21#[derive(Decode, Debug, Deserialize, Clone, PartialEq, Eq)]
22pub struct AccountData {
23	pub free: u128,
24	pub reserved: u128,
25	pub frozen: u128,
26	pub flags: u128,
27}
28
29/// The `AccountInfo` struct, reflecting the chain's state.
30#[derive(Decode, Debug, Deserialize, Clone, PartialEq, Eq)]
31pub struct AccountInfo {
32	pub nonce: u32,
33	pub consumers: u32,
34	pub providers: u32,
35	pub sufficients: u32,
36	pub data: AccountData,
37}
38
39/// Validate that a pallet exists in the chain metadata
40fn validate_pallet_exists(
41	client: &OnlineClient<ChainConfig>,
42	pallet_name: &str,
43) -> crate::error::Result<()> {
44	let metadata = client.metadata();
45	metadata.pallet_by_name(pallet_name).ok_or_else(|| {
46		QuantusError::Generic(format!(
47			"Pallet '{}' not found in chain metadata. Available pallets: {}",
48			pallet_name,
49			quantus_subxt::api::PALLETS.join(", ")
50		))
51	})?;
52	Ok(())
53}
54
55/// Direct interaction with chain storage (Sudo required for set)
56#[derive(Subcommand, Debug)]
57pub enum StorageCommands {
58	/// Get a storage value from a pallet.
59	///
60	/// This command constructs a storage key from the pallet and item names,
61	/// fetches the raw value from the chain state, and prints it as a hex string.
62	/// If --block is specified, queries storage at that specific block instead of latest.
63	/// If no --key is provided, automatically counts all entries in the storage map.
64	Get {
65		/// The name of the pallet (e.g., "System").
66		#[arg(long, required_unless_present = "storage_key")]
67		pallet: Option<String>,
68
69		/// The name of the storage item (e.g., "Account").
70		#[arg(long, required_unless_present = "storage_key")]
71		name: Option<String>,
72
73		/// Block number to query at a specific state.
74		#[arg(long)]
75		block: Option<String>,
76
77		/// Attempt to decode the value as a specific type (e.g., "u64", "accoundid",
78		/// "accountinfo").
79		#[arg(long)]
80		decode_as: Option<String>,
81
82		/// Storage key parameter (e.g., AccountId for System::Account)
83		#[arg(long, conflicts_with = "storage_key")]
84		key: Option<String>,
85
86		/// Type of the key component (e.g., "accountid", "u64").
87		#[arg(long, requires("key"))]
88		key_type: Option<String>,
89
90		/// Force counting all entries even when --key is provided (useful for debugging)
91		#[arg(long, conflicts_with = "storage_key")]
92		count: bool,
93
94		/// The full, final, hex-encoded storage key, as returned by `iterate` etc
95		#[arg(long, conflicts_with_all = &["pallet", "name", "key", "count"])]
96		storage_key: Option<String>,
97	},
98	/// List all storage items in a pallet.
99	///
100	/// Shows all available storage items with their metadata.
101	List {
102		/// The name of the pallet (e.g., "System")
103		#[arg(long)]
104		pallet: String,
105
106		/// Show only storage item names (no documentation)
107		#[arg(long)]
108		names_only: bool,
109	},
110	/// List all pallets that have storage items.
111	///
112	/// Shows all pallets with storage and optionally counts.
113	ListPallets {
114		/// Show counts of storage items per pallet
115		#[arg(long)]
116		with_counts: bool,
117	},
118	/// Show storage statistics and count information.
119	///
120	/// Displays statistics about storage usage.
121	Stats {
122		/// The name of the pallet (optional, shows all if not specified)
123		#[arg(long)]
124		pallet: Option<String>,
125
126		/// Show detailed statistics
127		#[arg(long)]
128		detailed: bool,
129	},
130	/// Iterate through storage map entries.
131	///
132	/// Useful for exploring storage maps and their contents.
133	Iterate {
134		/// The name of the pallet (e.g., "System")
135		#[arg(long)]
136		pallet: String,
137
138		/// The name of the storage item (e.g., "Account")
139		#[arg(long)]
140		name: String,
141
142		/// Maximum number of entries to show (use 0 to just count)
143		#[arg(long, default_value = "10")]
144		limit: u32,
145
146		/// Attempt to decode values as a specific type
147		#[arg(long)]
148		decode_as: Option<String>,
149
150		/// Block number or hash to query (optional, uses latest)
151		#[arg(long)]
152		block: Option<String>,
153	},
154
155	/// Set a storage value on the chain.
156	///
157	/// This requires sudo privileges. It constructs a `system.set_storage` call
158	/// and wraps it in a `sudo.sudo` extrinsic. The provided value should be
159	/// a hex-encoded SCALE representation of the value.
160	Set {
161		/// The name of the pallet (e.g., "Scheduler")
162		#[arg(long)]
163		pallet: String,
164
165		/// The name of the storage item (e.g., "LastProcessedTimestamp")
166		#[arg(long)]
167		name: String,
168
169		/// The new value. Can be a plain string if --type is used, otherwise a hex string.
170		#[arg(long)]
171		value: String,
172
173		/// The type of the value to be encoded (e.g., "u64", "moment", "accountid")
174		#[arg(long)]
175		r#type: Option<String>,
176
177		/// The name of the wallet to sign the transaction with (must have sudo rights)
178		#[arg(long)]
179		wallet: String,
180
181		/// The password for the wallet
182		#[arg(long)]
183		password: Option<String>,
184
185		/// Read password from file (for scripting)
186		#[arg(long)]
187		password_file: Option<String>,
188	},
189}
190
191/// Get block hash from block number or parse existing hash
192pub async fn resolve_block_hash(
193	quantus_client: &crate::chain::client::QuantusClient,
194	block_identifier: &str,
195) -> crate::error::Result<subxt::utils::H256> {
196	if block_identifier.starts_with("0x") {
197		// It's already a hash, parse it
198		subxt::utils::H256::from_str(block_identifier)
199			.map_err(|e| QuantusError::Generic(format!("Invalid block hash format: {e}")))
200	} else {
201		// It's a block number, convert to hash
202		let block_number = block_identifier.parse::<u32>().map_err(|e| {
203			QuantusError::Generic(format!("Invalid block number '{block_identifier}': {e}"))
204		})?;
205
206		log_verbose!("🔍 Converting block number {} to hash...", block_number);
207
208		use jsonrpsee::core::client::ClientT;
209		let block_hash: subxt::utils::H256 = quantus_client
210			.rpc_client()
211			.request::<subxt::utils::H256, [u32; 1]>("chain_getBlockHash", [block_number])
212			.await
213			.map_err(|e| {
214				QuantusError::NetworkError(format!(
215					"Failed to fetch block hash for block {block_number}: {e:?}"
216				))
217			})?;
218
219		log_verbose!("📦 Block {} hash: {:?}", block_number, block_hash);
220		Ok(block_hash)
221	}
222}
223
224/// Get raw storage value by key
225pub async fn get_storage_raw(
226	quantus_client: &crate::chain::client::QuantusClient,
227	key: Vec<u8>,
228) -> crate::error::Result<Option<Vec<u8>>> {
229	// Get the latest block hash to read from the latest state (not finalized)
230	let latest_block_hash = quantus_client.get_latest_block().await?;
231
232	let storage_at = quantus_client.client().storage().at(latest_block_hash);
233
234	let result = storage_at.fetch_raw(key).await?;
235
236	Ok(result)
237}
238
239/// Get raw storage value by key at specific block
240pub async fn get_storage_raw_at_block(
241	quantus_client: &crate::chain::client::QuantusClient,
242	key: Vec<u8>,
243	block_hash: subxt::utils::H256,
244) -> crate::error::Result<Option<Vec<u8>>> {
245	log_verbose!("🔍 Querying storage at block: {:?}", block_hash);
246
247	let storage_at = quantus_client.client().storage().at(block_hash);
248
249	let result = storage_at.fetch_raw(key).await?;
250
251	Ok(result)
252}
253
254/// Set storage value using sudo (requires sudo privileges)
255pub async fn set_storage_value(
256	quantus_client: &crate::chain::client::QuantusClient,
257	from_keypair: &crate::wallet::QuantumKeyPair,
258	storage_key: Vec<u8>,
259	value_bytes: Vec<u8>,
260) -> crate::error::Result<subxt::utils::H256> {
261	log_verbose!("✍️  Creating set_storage transaction...");
262
263	// Create the System::set_storage call using RuntimeCall type alias
264	let set_storage_call =
265		quantus_subxt::api::Call::System(quantus_subxt::api::system::Call::set_storage {
266			items: vec![(storage_key, value_bytes)],
267		});
268
269	// Wrap in Sudo::sudo call
270	let sudo_call = quantus_subxt::api::tx().sudo().sudo(set_storage_call);
271
272	let tx_hash =
273		crate::cli::common::submit_transaction(quantus_client, from_keypair, sudo_call, None)
274			.await?;
275
276	log_verbose!("📋 Set storage transaction submitted: {:?}", tx_hash);
277
278	Ok(tx_hash)
279}
280
281/// List all storage items in a pallet
282pub async fn list_storage_items(
283	quantus_client: &crate::chain::client::QuantusClient,
284	pallet_name: &str,
285	names_only: bool,
286) -> crate::error::Result<()> {
287	log_print!("📋 Listing storage items for pallet: {}", pallet_name.bright_green());
288
289	// Validate pallet exists
290	validate_pallet_exists(quantus_client.client(), pallet_name)?;
291
292	let metadata = quantus_client.client().metadata();
293	let pallet = metadata.pallet_by_name(pallet_name).unwrap();
294
295	if let Some(storage_metadata) = pallet.storage() {
296		let entries = storage_metadata.entries();
297		log_print!("Found {} storage items: \n", entries.len());
298
299		for (index, entry) in entries.iter().enumerate() {
300			log_print!(
301				"{}. {}",
302				(index + 1).to_string().bright_yellow(),
303				entry.name().bright_cyan()
304			);
305
306			if !names_only {
307				log_print!("   Type: {:?}", entry.entry_type());
308				if !entry.docs().is_empty() {
309					log_print!("   Docs: {}", entry.docs().join(" ").dimmed());
310				}
311				log_print!("");
312			}
313		}
314	} else {
315		log_print!("❌ Pallet '{}' has no storage items.", pallet_name.bright_red());
316	}
317
318	Ok(())
319}
320
321/// List all pallets with storage
322pub async fn list_pallets_with_storage(
323	quantus_client: &crate::chain::client::QuantusClient,
324	with_counts: bool,
325) -> crate::error::Result<()> {
326	log_print!("🏛️  Listing all pallets with storage:");
327	log_print!("");
328
329	let metadata = quantus_client.client().metadata();
330	let pallets: Vec<_> = metadata.pallets().collect();
331
332	let mut storage_pallets = BTreeMap::new();
333
334	for pallet in pallets {
335		if let Some(storage_metadata) = pallet.storage() {
336			let entry_count = storage_metadata.entries().len();
337			storage_pallets.insert(pallet.name(), entry_count);
338		}
339	}
340
341	if storage_pallets.is_empty() {
342		log_print!("❌ No pallets with storage found.");
343		return Ok(());
344	}
345
346	for (index, (pallet_name, count)) in storage_pallets.iter().enumerate() {
347		if with_counts {
348			log_print!(
349				"{}. {} ({} items)",
350				(index + 1).to_string().bright_yellow(),
351				pallet_name.bright_green(),
352				count.to_string().bright_blue()
353			);
354		} else {
355			log_print!(
356				"{}. {}",
357				(index + 1).to_string().bright_yellow(),
358				pallet_name.bright_green()
359			);
360		}
361	}
362
363	log_print!("");
364	log_print!("Total: {} pallets with storage", storage_pallets.len().to_string().bright_green());
365
366	Ok(())
367}
368
369/// Show storage statistics
370pub async fn show_storage_stats(
371	quantus_client: &crate::chain::client::QuantusClient,
372	pallet_name: Option<String>,
373	detailed: bool,
374) -> crate::error::Result<()> {
375	log_print!("📊 Storage size statistics: \n");
376
377	let metadata = quantus_client.client().metadata();
378
379	if let Some(pallet) = pallet_name {
380		// Show stats for specific pallet
381		validate_pallet_exists(quantus_client.client(), &pallet)?;
382		let pallet_meta = metadata.pallet_by_name(&pallet).unwrap();
383
384		if let Some(storage_metadata) = pallet_meta.storage() {
385			let entries = storage_metadata.entries();
386			log_print!("Pallet: {}", pallet.bright_green());
387			log_print!("Storage items: {}", entries.len().to_string().bright_blue());
388
389			if detailed {
390				log_print!("");
391				log_print!("Items:");
392				for (index, entry) in entries.iter().enumerate() {
393					log_print!(
394						"  {}. {} - {:?}",
395						(index + 1).to_string().dimmed(),
396						entry.name().bright_cyan(),
397						entry.entry_type()
398					);
399				}
400			}
401		} else {
402			log_print!("❌ Pallet '{}' has no storage items.", pallet.bright_red());
403		}
404	} else {
405		// Show global stats
406		let pallets: Vec<_> = metadata.pallets().collect();
407		let mut total_storage_items = 0;
408		let mut pallets_with_storage = 0;
409
410		let mut pallet_stats = Vec::new();
411
412		for pallet in pallets {
413			if let Some(storage_metadata) = pallet.storage() {
414				let entry_count = storage_metadata.entries().len();
415				total_storage_items += entry_count;
416				pallets_with_storage += 1;
417				pallet_stats.push((pallet.name(), entry_count));
418			}
419		}
420
421		log_print!("Total pallets: {}", metadata.pallets().len().to_string().bright_blue());
422		log_print!("Pallets with storage: {}", pallets_with_storage.to_string().bright_green());
423		log_print!("Total storage items: {}", total_storage_items.to_string().bright_yellow());
424
425		if detailed && !pallet_stats.is_empty() {
426			log_print!("");
427			log_print!("Per-pallet breakdown:");
428
429			// Sort by storage count (descending)
430			pallet_stats.sort_by(|a, b| b.1.cmp(&a.1));
431
432			for (pallet_name, count) in pallet_stats {
433				log_print!(
434					"  {} - {} items",
435					pallet_name.bright_cyan(),
436					count.to_string().bright_blue()
437				);
438			}
439		}
440	}
441
442	Ok(())
443}
444
445/// Count storage entries using RPC calls with pagination
446pub async fn count_storage_entries(
447	quantus_client: &crate::chain::client::QuantusClient,
448	pallet_name: &str,
449	storage_name: &str,
450	block_hash: subxt::utils::H256,
451) -> crate::error::Result<u32> {
452	// Construct storage key prefix for the storage item
453	let mut prefix = twox_128(pallet_name.as_bytes()).to_vec();
454	prefix.extend(&twox_128(storage_name.as_bytes()));
455
456	log_verbose!("🔑 Storage prefix for counting: 0x{}", hex::encode(&prefix));
457
458	use jsonrpsee::core::client::ClientT;
459
460	let block_hash_str = format!("{block_hash:#x}");
461	let prefix_hex = format!("0x{}", hex::encode(&prefix));
462	let page_size = 1000u32; // Max allowed per request
463	let mut total_count = 0u32;
464	let mut start_key: Option<String> = None;
465
466	loop {
467		// Use state_getKeysPaged RPC call to get keys with the prefix
468		let keys: Vec<String> = quantus_client
469			.rpc_client()
470			.request::<Vec<String>, (String, u32, Option<String>, Option<String>)>(
471				"state_getKeysPaged",
472				(
473					prefix_hex.clone(),           // prefix
474					page_size,                    // count
475					start_key.clone(),            // start_key for pagination
476					Some(block_hash_str.clone()), // at block
477				),
478			)
479			.await
480			.map_err(|e| {
481				QuantusError::NetworkError(format!(
482					"Failed to fetch storage keys at block {block_hash:?}: {e:?}"
483				))
484			})?;
485
486		let keys_count = keys.len() as u32;
487		total_count += keys_count;
488
489		log_verbose!("📊 Fetched {} keys (total: {})", keys_count, total_count);
490
491		// If we got less than page_size keys, we're done
492		if keys_count < page_size {
493			break;
494		}
495
496		// Set start_key to the last key for next iteration
497		start_key = keys.last().cloned();
498		if start_key.is_none() {
499			break;
500		}
501	}
502
503	Ok(total_count)
504}
505
506/// Iterate through storage map entries with real RPC calls
507pub async fn iterate_storage_entries(
508	quantus_client: &crate::chain::client::QuantusClient,
509	pallet_name: &str,
510	storage_name: &str,
511	limit: u32,
512	decode_as: Option<String>,
513	block_identifier: Option<String>,
514) -> crate::error::Result<()> {
515	log_print!(
516		"🔄 Iterating storage {}::{} (limit: {})",
517		pallet_name.bright_green(),
518		storage_name.bright_cyan(),
519		limit.to_string().bright_yellow()
520	);
521
522	// Validate pallet exists
523	validate_pallet_exists(quantus_client.client(), pallet_name)?;
524
525	// Determine block hash to use
526	let block_hash = if let Some(block_id) = block_identifier {
527		resolve_block_hash(quantus_client, &block_id).await?
528	} else {
529		quantus_client.get_latest_block().await?
530	};
531
532	log_verbose!("📦 Using block: {:?}", block_hash);
533
534	// Try to get storage metadata to show what type of storage this is
535	let metadata = quantus_client.client().metadata();
536	let pallet = metadata.pallet_by_name(pallet_name).unwrap();
537
538	if let Some(storage_metadata) = pallet.storage() {
539		if let Some(entry) = storage_metadata.entry_by_name(storage_name) {
540			log_print!("📝 Storage type: {:?}", entry.entry_type());
541			if !entry.docs().is_empty() {
542				log_print!("📖 Docs: {}", entry.docs().join(" ").dimmed());
543			}
544		}
545	}
546
547	// Count total entries
548	log_print!("🔢 Counting storage entries...");
549	let total_count =
550		count_storage_entries(quantus_client, pallet_name, storage_name, block_hash).await?;
551
552	log_success!(
553		"📊 Total entries in {}::{}: {}",
554		pallet_name.bright_green(),
555		storage_name.bright_cyan(),
556		total_count.to_string().bright_yellow()
557	);
558
559	// If limit is 0, just show count
560	if limit == 0 {
561		return Ok(());
562	}
563
564	// Construct storage key prefix for the storage item
565	let mut prefix = twox_128(pallet_name.as_bytes()).to_vec();
566	prefix.extend(&twox_128(storage_name.as_bytes()));
567
568	log_verbose!("🔑 Storage prefix: 0x{}", hex::encode(&prefix));
569
570	// Use RPC to get keys with pagination
571	use jsonrpsee::core::client::ClientT;
572
573	let block_hash_str = format!("{block_hash:#x}");
574	let keys: Vec<String> = quantus_client
575		.rpc_client()
576		.request::<Vec<String>, (String, u32, Option<String>, Option<String>)>(
577			"state_getKeysPaged",
578			(
579				format!("0x{}", hex::encode(&prefix)),
580				limit,                // limit entries
581				None,                 // start_key
582				Some(block_hash_str), // at block
583			),
584		)
585		.await
586		.map_err(|e| {
587			QuantusError::NetworkError(format!(
588				"Failed to fetch storage keys at block {block_hash:?}: {e:?}"
589			))
590		})?;
591
592	if keys.is_empty() {
593		log_print!("❌ No entries found.");
594		return Ok(());
595	}
596
597	log_print!("📋 First {} entries:", keys.len().min(limit as usize));
598	log_print!("");
599
600	// Show first few keys and optionally their values
601	for (index, key) in keys.iter().take(limit as usize).enumerate() {
602		log_print!("{}. Key: {}", (index + 1).to_string().bright_yellow(), key.dimmed());
603
604		// Optionally fetch and decode values (only for first few to avoid spam)
605		if index < 3 && decode_as.is_some() {
606			if let Ok(key_bytes) = hex::decode(key.strip_prefix("0x").unwrap_or(key)) {
607				if let Ok(Some(value_bytes)) =
608					get_storage_raw_at_block(quantus_client, key_bytes, block_hash).await
609				{
610					if let Some(ref decode_type) = decode_as {
611						match decode_storage_value(&value_bytes, decode_type) {
612							Ok(decoded_value) =>
613								log_print!("   Value: {}", decoded_value.bright_green()),
614							Err(_) => log_print!(
615								"   Value: 0x{} (raw)",
616								hex::encode(&value_bytes).dimmed()
617							),
618						}
619					}
620				}
621			}
622		}
623	}
624
625	if total_count > limit {
626		log_print!("");
627		log_print!(
628			"... and {} more entries (use --limit 0 to just count)",
629			(total_count - limit).to_string().bright_blue()
630		);
631	}
632
633	Ok(())
634}
635
636/// Decode storage value based on type
637fn decode_storage_value(value_bytes: &[u8], type_str: &str) -> crate::error::Result<String> {
638	match type_str.to_lowercase().as_str() {
639		"u32" => match u32::decode(&mut &value_bytes[..]) {
640			Ok(decoded_value) => Ok(decoded_value.to_string()),
641			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u32: {e}"))),
642		},
643		"u64" | "moment" => match u64::decode(&mut &value_bytes[..]) {
644			Ok(decoded_value) => Ok(decoded_value.to_string()),
645			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u64: {e}"))),
646		},
647		"u128" | "balance" => match u128::decode(&mut &value_bytes[..]) {
648			Ok(decoded_value) => Ok(decoded_value.to_string()),
649			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u128: {e}"))),
650		},
651		"accountid" | "accountid32" => match AccountId32::decode(&mut &value_bytes[..]) {
652			Ok(account_id) => Ok(account_id.to_quantus_ss58()),
653			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as AccountId32: {e}"))),
654		},
655		"accountinfo" => match AccountInfo::decode(&mut &value_bytes[..]) {
656			Ok(account_info) => Ok(format!("{account_info:#?}")),
657			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as AccountInfo: {e}"))),
658		},
659		_ => Err(QuantusError::Generic(format!(
660			"Unsupported type for decoding: {type_str}. Supported types: u32, u64, moment, u128, balance, accountid, accountinfo"
661		))),
662	}
663}
664
665/// Get storage by a full, hex-encoded storage key.
666async fn get_storage_by_storage_key(
667	quantus_client: &crate::chain::client::QuantusClient,
668	storage_key: String,
669	block: Option<String>,
670	decode_as: Option<String>,
671) -> crate::error::Result<()> {
672	log_print!("🗄️  Storage");
673
674	let encoded_storage_key = encode_storage_key(&storage_key, "raw")?;
675
676	let result = if let Some(block_id) = block {
677		let block_hash = resolve_block_hash(quantus_client, &block_id).await?;
678		get_storage_raw_at_block(quantus_client, encoded_storage_key, block_hash).await?
679	} else {
680		get_storage_raw(quantus_client, encoded_storage_key).await?
681	};
682
683	if let Some(value_bytes) = result {
684		log_success!("Raw Value: 0x{}", hex::encode(&value_bytes).bright_yellow());
685		if let Some(type_str) = decode_as {
686			log_print!("Attempting to decode as {}...", type_str.bright_cyan());
687			match decode_storage_value(&value_bytes, &type_str) {
688				Ok(decoded_value) =>
689					log_success!("Decoded Value: {}", decoded_value.bright_green()),
690				Err(e) => log_error!("{}", e),
691			}
692		}
693	} else {
694		log_print!("{}", "No value found at this storage location.".dimmed());
695	}
696
697	Ok(())
698}
699
700/// Get storage by providing its pallet, name, and optional key component.
701async fn get_storage_by_parts(
702	quantus_client: &crate::chain::client::QuantusClient,
703	pallet: String,
704	name: String,
705	key: Option<String>,
706	key_type: Option<String>,
707	block: Option<String>,
708	decode_as: Option<String>,
709	count: bool,
710) -> crate::error::Result<()> {
711	if let Some(block_value) = &block {
712		log_print!(
713			"🔎 Getting storage for {}::{} at block {}",
714			pallet.bright_green(),
715			name.bright_cyan(),
716			block_value.bright_yellow()
717		);
718	} else {
719		log_print!(
720			"🔎 Getting storage for {}::{} (latest block)",
721			pallet.bright_green(),
722			name.bright_cyan()
723		);
724	}
725
726	if let Some(key_value) = &key {
727		log_print!("🔑 With key: {}", key_value.bright_yellow());
728	}
729
730	validate_pallet_exists(quantus_client.client(), &pallet)?;
731
732	let block_hash = if let Some(block_id) = &block {
733		resolve_block_hash(quantus_client, block_id).await?
734	} else {
735		quantus_client.get_latest_block().await?
736	};
737
738	let entry_count = count_storage_entries(quantus_client, &pallet, &name, block_hash).await?;
739	let is_storage_value = entry_count == 1;
740
741	let should_count = count || (key.is_none() && !is_storage_value);
742
743	if should_count {
744		log_print!("🔢 Counting all entries in {}::{}", pallet.bright_green(), name.bright_cyan());
745
746		let block_display = if let Some(ref block_id) = block {
747			format!(" at block {}", block_id.bright_yellow())
748		} else {
749			" (latest)".to_string()
750		};
751
752		log_success!(
753			"👥 Total entries{}: {}",
754			block_display,
755			entry_count.to_string().bright_green().bold()
756		);
757	} else {
758		let mut storage_key = twox_128(pallet.as_bytes()).to_vec();
759		storage_key.extend(&twox_128(name.as_bytes()));
760
761		if let Some(key_value) = &key {
762			if let Some(key_type_str) = &key_type {
763				let key_bytes = encode_storage_key(key_value, key_type_str)?;
764				storage_key.extend(key_bytes);
765			} else {
766				log_error!("Key type (--key-type) is required when using --key parameter");
767				return Ok(());
768			}
769		} else if !is_storage_value {
770			log_print!("🔢 This is a storage map with {} entries. Use --key to get a specific value or omit --key to count all entries.", entry_count);
771			return Ok(());
772		}
773
774		let result = get_storage_raw_at_block(quantus_client, storage_key, block_hash).await?;
775
776		if let Some(value_bytes) = result {
777			log_success!("Raw Value: 0x{}", hex::encode(&value_bytes).bright_yellow());
778
779			if let Some(type_str) = decode_as {
780				log_print!("Attempting to decode as {}...", type_str.bright_cyan());
781				match decode_storage_value(&value_bytes, &type_str) {
782					Ok(decoded_value) =>
783						log_success!("Decoded Value: {}", decoded_value.bright_green()),
784					Err(e) => log_error!("{}", e),
785				}
786			}
787		} else {
788			log_print!("{}", "No value found at this storage location.".dimmed());
789		}
790	}
791
792	Ok(())
793}
794
795/// Handle storage subxt commands
796pub async fn handle_storage_command(
797	command: StorageCommands,
798	node_url: &str,
799) -> crate::error::Result<()> {
800	log_print!("🗄️  Storage");
801
802	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
803
804	match command {
805		StorageCommands::Get {
806			pallet,
807			name,
808			block,
809			decode_as,
810			key,
811			key_type,
812			count,
813			storage_key,
814		} => {
815			if let Some(s_key) = storage_key {
816				get_storage_by_storage_key(&quantus_client, s_key, block, decode_as).await
817			} else {
818				// Clap ensures that pallet and name are present if `storage_key` is None
819				get_storage_by_parts(
820					&quantus_client,
821					pallet.unwrap(),
822					name.unwrap(),
823					key,
824					key_type,
825					block,
826					decode_as,
827					count,
828				)
829				.await
830			}
831		},
832		StorageCommands::List { pallet, names_only } =>
833			list_storage_items(&quantus_client, &pallet, names_only).await,
834		StorageCommands::ListPallets { with_counts } =>
835			list_pallets_with_storage(&quantus_client, with_counts).await,
836		StorageCommands::Stats { pallet, detailed } =>
837			show_storage_stats(&quantus_client, pallet, detailed).await,
838		StorageCommands::Iterate { pallet, name, limit, decode_as, block } =>
839			iterate_storage_entries(&quantus_client, &pallet, &name, limit, decode_as, block).await,
840
841		StorageCommands::Set { pallet, name, value, wallet, password, password_file, r#type } => {
842			log_print!("✍️  Setting storage for {}::{}", pallet.bright_green(), name.bright_cyan());
843			log_print!("\n{}", "🛑 This is a SUDO operation!".bright_red().bold());
844
845			// Validate pallet exists
846			validate_pallet_exists(quantus_client.client(), &pallet)?;
847
848			// 1. Load wallet
849			let keypair =
850				crate::wallet::load_keypair_from_wallet(&wallet, password, password_file)?;
851			log_verbose!("🔐 Using wallet: {}", wallet.bright_green());
852
853			// 2. Encode the value based on the --type flag
854			let value_bytes = match r#type.as_deref() {
855				Some("u64") | Some("moment") => value
856					.parse::<u64>()
857					.map_err(|e| QuantusError::Generic(format!("Invalid u64 value: {e}")))?
858					.encode(),
859				Some("u128") | Some("balance") => value
860					.parse::<u128>()
861					.map_err(|e| QuantusError::Generic(format!("Invalid u128 value: {e}")))?
862					.encode(),
863				Some("accountid") | Some("accountid32") => AccountId32::from_ss58check(&value)
864					.map_err(|e| QuantusError::Generic(format!("Invalid AccountId value: {e:?}")))?
865					.encode(),
866				None => {
867					// Default to hex decoding if no type is specified
868					// Try to parse as H256 first, then fall back to hex decode
869					if value.starts_with("0x") && value.len() == 66 {
870						// 0x + 64 hex chars = 66 (32 bytes)
871						// Try to parse as H256
872						let h256_value = subxt::utils::H256::from_str(&value).map_err(|e| {
873							QuantusError::Generic(format!("Invalid H256 value: {e}"))
874						})?;
875						h256_value.0.to_vec()
876					} else {
877						// Fall back to hex decode for other hex values
878						let value_hex = value.strip_prefix("0x").unwrap_or(&value);
879						hex::decode(value_hex)
880							.map_err(|e| QuantusError::Generic(format!("Invalid hex value: {e}")))?
881					}
882				},
883				Some(unsupported) =>
884					return Err(QuantusError::Generic(format!(
885						"Unsupported type for --type: {unsupported}"
886					))),
887			};
888
889			log_verbose!("Encoded value bytes: 0x{}", hex::encode(&value_bytes).dimmed());
890
891			// 3. Construct the storage key
892			let storage_key = {
893				let mut key = twox_128(pallet.as_bytes()).to_vec();
894				key.extend(&twox_128(name.as_bytes()));
895				key
896			};
897
898			// 4. Submit the set storage transaction
899			let tx_hash =
900				set_storage_value(&quantus_client, &keypair, storage_key, value_bytes).await?;
901
902			log_print!(
903				"✅ {} Set storage transaction submitted! Hash: {:?}",
904				"SUCCESS".bright_green().bold(),
905				tx_hash
906			);
907
908			let success = wait_for_tx_confirmation(quantus_client.client(), tx_hash).await?;
909
910			if success {
911				log_success!(
912					"🎉 {} Set storage transaction confirmed!",
913					"FINISHED".bright_green().bold()
914				);
915			} else {
916				log_error!("Transaction failed!");
917			}
918
919			Ok(())
920		},
921	}
922}
923
924/// Encode storage key parameter based on type
925fn encode_storage_key(key_value: &str, key_type: &str) -> crate::error::Result<Vec<u8>> {
926	use codec::Encode;
927	use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
928
929	match key_type.to_lowercase().as_str() {
930		"accountid" | "accountid32" => {
931			let account_id = SpAccountId32::from_ss58check(key_value).map_err(|e| {
932				crate::error::QuantusError::Generic(format!("Invalid AccountId: {e:?}"))
933			})?;
934			Ok(account_id.encode())
935		},
936		"u64" => {
937			let value = key_value
938				.parse::<u64>()
939				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid u64: {e}")))?;
940			Ok(value.encode())
941		},
942		"u128" => {
943			let value = key_value
944				.parse::<u128>()
945				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid u128: {e}")))?;
946			Ok(value.encode())
947		},
948		"u32" => {
949			let value = key_value
950				.parse::<u32>()
951				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid u32: {e}")))?;
952			Ok(value.encode())
953		},
954		"hex" | "raw" => {
955			// For hex/raw keys, decode the hex string directly
956			let value_hex = key_value.strip_prefix("0x").unwrap_or(key_value);
957			hex::decode(value_hex)
958				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid hex value: {e}")))
959		},
960		_ => Err(crate::error::QuantusError::Generic(format!(
961			"Unsupported key type: {key_type}. Supported types: accountid, u64, u128, u32, hex, raw"
962		))),
963	}
964}