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,
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	finalized: bool,
261) -> crate::error::Result<subxt::utils::H256> {
262	log_verbose!("✍️  Creating set_storage transaction...");
263
264	// Create the System::set_storage call using RuntimeCall type alias
265	let set_storage_call =
266		quantus_subxt::api::Call::System(quantus_subxt::api::system::Call::set_storage {
267			items: vec![(storage_key, value_bytes)],
268		});
269
270	// Wrap in Sudo::sudo call
271	let sudo_call = quantus_subxt::api::tx().sudo().sudo(set_storage_call);
272
273	let tx_hash = crate::cli::common::submit_transaction(
274		quantus_client,
275		from_keypair,
276		sudo_call,
277		None,
278		finalized,
279	)
280	.await?;
281
282	log_verbose!("📋 Set storage transaction submitted: {:?}", tx_hash);
283
284	Ok(tx_hash)
285}
286
287/// List all storage items in a pallet
288pub async fn list_storage_items(
289	quantus_client: &crate::chain::client::QuantusClient,
290	pallet_name: &str,
291	names_only: bool,
292) -> crate::error::Result<()> {
293	log_print!("📋 Listing storage items for pallet: {}", pallet_name.bright_green());
294
295	// Validate pallet exists
296	validate_pallet_exists(quantus_client.client(), pallet_name)?;
297
298	let metadata = quantus_client.client().metadata();
299	let pallet = metadata.pallet_by_name(pallet_name).unwrap();
300
301	if let Some(storage_metadata) = pallet.storage() {
302		let entries = storage_metadata.entries();
303		log_print!("Found {} storage items: \n", entries.len());
304
305		for (index, entry) in entries.iter().enumerate() {
306			log_print!(
307				"{}. {}",
308				(index + 1).to_string().bright_yellow(),
309				entry.name().bright_cyan()
310			);
311
312			if !names_only {
313				log_print!("   Type: {:?}", entry.entry_type());
314				if !entry.docs().is_empty() {
315					log_print!("   Docs: {}", entry.docs().join(" ").dimmed());
316				}
317				log_print!("");
318			}
319		}
320	} else {
321		log_print!("❌ Pallet '{}' has no storage items.", pallet_name.bright_red());
322	}
323
324	Ok(())
325}
326
327/// List all pallets with storage
328pub async fn list_pallets_with_storage(
329	quantus_client: &crate::chain::client::QuantusClient,
330	with_counts: bool,
331) -> crate::error::Result<()> {
332	log_print!("🏛️  Listing all pallets with storage:");
333	log_print!("");
334
335	let metadata = quantus_client.client().metadata();
336	let pallets: Vec<_> = metadata.pallets().collect();
337
338	let mut storage_pallets = BTreeMap::new();
339
340	for pallet in pallets {
341		if let Some(storage_metadata) = pallet.storage() {
342			let entry_count = storage_metadata.entries().len();
343			storage_pallets.insert(pallet.name(), entry_count);
344		}
345	}
346
347	if storage_pallets.is_empty() {
348		log_print!("❌ No pallets with storage found.");
349		return Ok(());
350	}
351
352	for (index, (pallet_name, count)) in storage_pallets.iter().enumerate() {
353		if with_counts {
354			log_print!(
355				"{}. {} ({} items)",
356				(index + 1).to_string().bright_yellow(),
357				pallet_name.bright_green(),
358				count.to_string().bright_blue()
359			);
360		} else {
361			log_print!(
362				"{}. {}",
363				(index + 1).to_string().bright_yellow(),
364				pallet_name.bright_green()
365			);
366		}
367	}
368
369	log_print!("");
370	log_print!("Total: {} pallets with storage", storage_pallets.len().to_string().bright_green());
371
372	Ok(())
373}
374
375/// Show storage statistics
376pub async fn show_storage_stats(
377	quantus_client: &crate::chain::client::QuantusClient,
378	pallet_name: Option<String>,
379	detailed: bool,
380) -> crate::error::Result<()> {
381	log_print!("📊 Storage size statistics: \n");
382
383	let metadata = quantus_client.client().metadata();
384
385	if let Some(pallet) = pallet_name {
386		// Show stats for specific pallet
387		validate_pallet_exists(quantus_client.client(), &pallet)?;
388		let pallet_meta = metadata.pallet_by_name(&pallet).unwrap();
389
390		if let Some(storage_metadata) = pallet_meta.storage() {
391			let entries = storage_metadata.entries();
392			log_print!("Pallet: {}", pallet.bright_green());
393			log_print!("Storage items: {}", entries.len().to_string().bright_blue());
394
395			if detailed {
396				log_print!("");
397				log_print!("Items:");
398				for (index, entry) in entries.iter().enumerate() {
399					log_print!(
400						"  {}. {} - {:?}",
401						(index + 1).to_string().dimmed(),
402						entry.name().bright_cyan(),
403						entry.entry_type()
404					);
405				}
406			}
407		} else {
408			log_print!("❌ Pallet '{}' has no storage items.", pallet.bright_red());
409		}
410	} else {
411		// Show global stats
412		let pallets: Vec<_> = metadata.pallets().collect();
413		let mut total_storage_items = 0;
414		let mut pallets_with_storage = 0;
415
416		let mut pallet_stats = Vec::new();
417
418		for pallet in pallets {
419			if let Some(storage_metadata) = pallet.storage() {
420				let entry_count = storage_metadata.entries().len();
421				total_storage_items += entry_count;
422				pallets_with_storage += 1;
423				pallet_stats.push((pallet.name(), entry_count));
424			}
425		}
426
427		log_print!("Total pallets: {}", metadata.pallets().len().to_string().bright_blue());
428		log_print!("Pallets with storage: {}", pallets_with_storage.to_string().bright_green());
429		log_print!("Total storage items: {}", total_storage_items.to_string().bright_yellow());
430
431		if detailed && !pallet_stats.is_empty() {
432			log_print!("");
433			log_print!("Per-pallet breakdown:");
434
435			// Sort by storage count (descending)
436			pallet_stats.sort_by(|a, b| b.1.cmp(&a.1));
437
438			for (pallet_name, count) in pallet_stats {
439				log_print!(
440					"  {} - {} items",
441					pallet_name.bright_cyan(),
442					count.to_string().bright_blue()
443				);
444			}
445		}
446	}
447
448	Ok(())
449}
450
451/// Count storage entries using RPC calls with pagination
452pub async fn count_storage_entries(
453	quantus_client: &crate::chain::client::QuantusClient,
454	pallet_name: &str,
455	storage_name: &str,
456	block_hash: subxt::utils::H256,
457) -> crate::error::Result<u32> {
458	// Construct storage key prefix for the storage item
459	let mut prefix = twox_128(pallet_name.as_bytes()).to_vec();
460	prefix.extend(&twox_128(storage_name.as_bytes()));
461
462	log_verbose!("🔑 Storage prefix for counting: 0x{}", hex::encode(&prefix));
463
464	use jsonrpsee::core::client::ClientT;
465
466	let block_hash_str = format!("{block_hash:#x}");
467	let prefix_hex = format!("0x{}", hex::encode(&prefix));
468	let page_size = 1000u32; // Max allowed per request
469	let mut total_count = 0u32;
470	let mut start_key: Option<String> = None;
471
472	loop {
473		// Use state_getKeysPaged RPC call to get keys with the prefix
474		let keys: Vec<String> = quantus_client
475			.rpc_client()
476			.request::<Vec<String>, (String, u32, Option<String>, Option<String>)>(
477				"state_getKeysPaged",
478				(
479					prefix_hex.clone(),           // prefix
480					page_size,                    // count
481					start_key.clone(),            // start_key for pagination
482					Some(block_hash_str.clone()), // at block
483				),
484			)
485			.await
486			.map_err(|e| {
487				QuantusError::NetworkError(format!(
488					"Failed to fetch storage keys at block {block_hash:?}: {e:?}"
489				))
490			})?;
491
492		let keys_count = keys.len() as u32;
493		total_count += keys_count;
494
495		log_verbose!("📊 Fetched {} keys (total: {})", keys_count, total_count);
496
497		// If we got less than page_size keys, we're done
498		if keys_count < page_size {
499			break;
500		}
501
502		// Set start_key to the last key for next iteration
503		start_key = keys.last().cloned();
504		if start_key.is_none() {
505			break;
506		}
507	}
508
509	Ok(total_count)
510}
511
512/// Iterate through storage map entries with real RPC calls
513pub async fn iterate_storage_entries(
514	quantus_client: &crate::chain::client::QuantusClient,
515	pallet_name: &str,
516	storage_name: &str,
517	limit: u32,
518	decode_as: Option<String>,
519	block_identifier: Option<String>,
520) -> crate::error::Result<()> {
521	log_print!(
522		"🔄 Iterating storage {}::{} (limit: {})",
523		pallet_name.bright_green(),
524		storage_name.bright_cyan(),
525		limit.to_string().bright_yellow()
526	);
527
528	// Validate pallet exists
529	validate_pallet_exists(quantus_client.client(), pallet_name)?;
530
531	// Determine block hash to use
532	let block_hash = if let Some(block_id) = block_identifier {
533		resolve_block_hash(quantus_client, &block_id).await?
534	} else {
535		quantus_client.get_latest_block().await?
536	};
537
538	log_verbose!("📦 Using block: {:?}", block_hash);
539
540	// Try to get storage metadata to show what type of storage this is
541	let metadata = quantus_client.client().metadata();
542	let pallet = metadata.pallet_by_name(pallet_name).unwrap();
543
544	if let Some(storage_metadata) = pallet.storage() {
545		if let Some(entry) = storage_metadata.entry_by_name(storage_name) {
546			log_print!("📝 Storage type: {:?}", entry.entry_type());
547			if !entry.docs().is_empty() {
548				log_print!("📖 Docs: {}", entry.docs().join(" ").dimmed());
549			}
550		}
551	}
552
553	// Count total entries
554	log_print!("🔢 Counting storage entries...");
555	let total_count =
556		count_storage_entries(quantus_client, pallet_name, storage_name, block_hash).await?;
557
558	log_success!(
559		"📊 Total entries in {}::{}: {}",
560		pallet_name.bright_green(),
561		storage_name.bright_cyan(),
562		total_count.to_string().bright_yellow()
563	);
564
565	// If limit is 0, just show count
566	if limit == 0 {
567		return Ok(());
568	}
569
570	// Construct storage key prefix for the storage item
571	let mut prefix = twox_128(pallet_name.as_bytes()).to_vec();
572	prefix.extend(&twox_128(storage_name.as_bytes()));
573
574	log_verbose!("🔑 Storage prefix: 0x{}", hex::encode(&prefix));
575
576	// Use RPC to get keys with pagination
577	use jsonrpsee::core::client::ClientT;
578
579	let block_hash_str = format!("{block_hash:#x}");
580	let keys: Vec<String> = quantus_client
581		.rpc_client()
582		.request::<Vec<String>, (String, u32, Option<String>, Option<String>)>(
583			"state_getKeysPaged",
584			(
585				format!("0x{}", hex::encode(&prefix)),
586				limit,                // limit entries
587				None,                 // start_key
588				Some(block_hash_str), // at block
589			),
590		)
591		.await
592		.map_err(|e| {
593			QuantusError::NetworkError(format!(
594				"Failed to fetch storage keys at block {block_hash:?}: {e:?}"
595			))
596		})?;
597
598	if keys.is_empty() {
599		log_print!("❌ No entries found.");
600		return Ok(());
601	}
602
603	log_print!("📋 First {} entries:", keys.len().min(limit as usize));
604	log_print!("");
605
606	// Show first few keys and optionally their values
607	for (index, key) in keys.iter().take(limit as usize).enumerate() {
608		log_print!("{}. Key: {}", (index + 1).to_string().bright_yellow(), key.dimmed());
609
610		// Optionally fetch and decode values (only for first few to avoid spam)
611		if index < 3 && decode_as.is_some() {
612			if let Ok(key_bytes) = hex::decode(key.strip_prefix("0x").unwrap_or(key)) {
613				if let Ok(Some(value_bytes)) =
614					get_storage_raw_at_block(quantus_client, key_bytes, block_hash).await
615				{
616					if let Some(ref decode_type) = decode_as {
617						match decode_storage_value(&value_bytes, decode_type) {
618							Ok(decoded_value) => {
619								log_print!("   Value: {}", decoded_value.bright_green())
620							},
621							Err(_) => log_print!(
622								"   Value: 0x{} (raw)",
623								hex::encode(&value_bytes).dimmed()
624							),
625						}
626					}
627				}
628			}
629		}
630	}
631
632	if total_count > limit {
633		log_print!("");
634		log_print!(
635			"... and {} more entries (use --limit 0 to just count)",
636			(total_count - limit).to_string().bright_blue()
637		);
638	}
639
640	Ok(())
641}
642
643/// Decode storage value based on type
644fn decode_storage_value(value_bytes: &[u8], type_str: &str) -> crate::error::Result<String> {
645	match type_str.to_lowercase().as_str() {
646		"u32" => match u32::decode(&mut &value_bytes[..]) {
647			Ok(decoded_value) => Ok(decoded_value.to_string()),
648			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u32: {e}"))),
649		},
650		"u64" | "moment" => match u64::decode(&mut &value_bytes[..]) {
651			Ok(decoded_value) => Ok(decoded_value.to_string()),
652			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u64: {e}"))),
653		},
654		"u128" | "balance" => match u128::decode(&mut &value_bytes[..]) {
655			Ok(decoded_value) => Ok(decoded_value.to_string()),
656			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u128: {e}"))),
657		},
658		"accountid" | "accountid32" => match AccountId32::decode(&mut &value_bytes[..]) {
659			Ok(account_id) => Ok(account_id.to_quantus_ss58()),
660			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as AccountId32: {e}"))),
661		},
662		"accountinfo" => match AccountInfo::decode(&mut &value_bytes[..]) {
663			Ok(account_info) => Ok(format!("{account_info:#?}")),
664			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as AccountInfo: {e}"))),
665		},
666		_ => Err(QuantusError::Generic(format!(
667			"Unsupported type for decoding: {type_str}. Supported types: u32, u64, moment, u128, balance, accountid, accountinfo"
668		))),
669	}
670}
671
672/// Get storage by a full, hex-encoded storage key.
673async fn get_storage_by_storage_key(
674	quantus_client: &crate::chain::client::QuantusClient,
675	storage_key: String,
676	block: Option<String>,
677	decode_as: Option<String>,
678) -> crate::error::Result<()> {
679	log_print!("🗄️  Storage");
680
681	let encoded_storage_key = encode_storage_key(&storage_key, "raw")?;
682
683	let result = if let Some(block_id) = block {
684		let block_hash = resolve_block_hash(quantus_client, &block_id).await?;
685		get_storage_raw_at_block(quantus_client, encoded_storage_key, block_hash).await?
686	} else {
687		get_storage_raw(quantus_client, encoded_storage_key).await?
688	};
689
690	if let Some(value_bytes) = result {
691		log_success!("Raw Value: 0x{}", hex::encode(&value_bytes).bright_yellow());
692		if let Some(type_str) = decode_as {
693			log_print!("Attempting to decode as {}...", type_str.bright_cyan());
694			match decode_storage_value(&value_bytes, &type_str) {
695				Ok(decoded_value) => {
696					log_success!("Decoded Value: {}", decoded_value.bright_green())
697				},
698				Err(e) => log_error!("{}", e),
699			}
700		}
701	} else {
702		log_print!("{}", "No value found at this storage location.".dimmed());
703	}
704
705	Ok(())
706}
707
708/// Get storage by providing its pallet, name, and optional key component.
709async fn get_storage_by_parts(
710	quantus_client: &crate::chain::client::QuantusClient,
711	pallet: String,
712	name: String,
713	key: Option<String>,
714	key_type: Option<String>,
715	block: Option<String>,
716	decode_as: Option<String>,
717	count: bool,
718) -> crate::error::Result<()> {
719	if let Some(block_value) = &block {
720		log_print!(
721			"🔎 Getting storage for {}::{} at block {}",
722			pallet.bright_green(),
723			name.bright_cyan(),
724			block_value.bright_yellow()
725		);
726	} else {
727		log_print!(
728			"🔎 Getting storage for {}::{} (latest block)",
729			pallet.bright_green(),
730			name.bright_cyan()
731		);
732	}
733
734	if let Some(key_value) = &key {
735		log_print!("🔑 With key: {}", key_value.bright_yellow());
736	}
737
738	validate_pallet_exists(quantus_client.client(), &pallet)?;
739
740	let block_hash = if let Some(block_id) = &block {
741		resolve_block_hash(quantus_client, block_id).await?
742	} else {
743		quantus_client.get_latest_block().await?
744	};
745
746	let entry_count = count_storage_entries(quantus_client, &pallet, &name, block_hash).await?;
747	let is_storage_value = entry_count == 1;
748
749	let should_count = count || (key.is_none() && !is_storage_value);
750
751	if should_count {
752		log_print!("🔢 Counting all entries in {}::{}", pallet.bright_green(), name.bright_cyan());
753
754		let block_display = if let Some(ref block_id) = block {
755			format!(" at block {}", block_id.bright_yellow())
756		} else {
757			" (latest)".to_string()
758		};
759
760		log_success!(
761			"👥 Total entries{}: {}",
762			block_display,
763			entry_count.to_string().bright_green().bold()
764		);
765	} else {
766		let mut storage_key = twox_128(pallet.as_bytes()).to_vec();
767		storage_key.extend(&twox_128(name.as_bytes()));
768
769		if let Some(key_value) = &key {
770			if let Some(key_type_str) = &key_type {
771				let key_bytes = encode_storage_key(key_value, key_type_str)?;
772				storage_key.extend(key_bytes);
773			} else {
774				log_error!("Key type (--key-type) is required when using --key parameter");
775				return Ok(());
776			}
777		} else if !is_storage_value {
778			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);
779			return Ok(());
780		}
781
782		let result = get_storage_raw_at_block(quantus_client, storage_key, block_hash).await?;
783
784		if let Some(value_bytes) = result {
785			log_success!("Raw Value: 0x{}", hex::encode(&value_bytes).bright_yellow());
786
787			if let Some(type_str) = decode_as {
788				log_print!("Attempting to decode as {}...", type_str.bright_cyan());
789				match decode_storage_value(&value_bytes, &type_str) {
790					Ok(decoded_value) => {
791						log_success!("Decoded Value: {}", decoded_value.bright_green())
792					},
793					Err(e) => log_error!("{}", e),
794				}
795			}
796		} else {
797			log_print!("{}", "No value found at this storage location.".dimmed());
798		}
799	}
800
801	Ok(())
802}
803
804/// Handle storage subxt commands
805pub async fn handle_storage_command(
806	command: StorageCommands,
807	node_url: &str,
808	finalized: bool,
809) -> crate::error::Result<()> {
810	log_print!("🗄️  Storage");
811
812	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
813
814	match command {
815		StorageCommands::Get {
816			pallet,
817			name,
818			block,
819			decode_as,
820			key,
821			key_type,
822			count,
823			storage_key,
824		} => {
825			if let Some(s_key) = storage_key {
826				get_storage_by_storage_key(&quantus_client, s_key, block, decode_as).await
827			} else {
828				// Clap ensures that pallet and name are present if `storage_key` is None
829				get_storage_by_parts(
830					&quantus_client,
831					pallet.unwrap(),
832					name.unwrap(),
833					key,
834					key_type,
835					block,
836					decode_as,
837					count,
838				)
839				.await
840			}
841		},
842		StorageCommands::List { pallet, names_only } =>
843			list_storage_items(&quantus_client, &pallet, names_only).await,
844		StorageCommands::ListPallets { with_counts } =>
845			list_pallets_with_storage(&quantus_client, with_counts).await,
846		StorageCommands::Stats { pallet, detailed } =>
847			show_storage_stats(&quantus_client, pallet, detailed).await,
848		StorageCommands::Iterate { pallet, name, limit, decode_as, block } =>
849			iterate_storage_entries(&quantus_client, &pallet, &name, limit, decode_as, block).await,
850
851		StorageCommands::Set { pallet, name, value, wallet, password, password_file, r#type } => {
852			log_print!("✍️  Setting storage for {}::{}", pallet.bright_green(), name.bright_cyan());
853			log_print!("\n{}", "🛑 This is a SUDO operation!".bright_red().bold());
854
855			// Validate pallet exists
856			validate_pallet_exists(quantus_client.client(), &pallet)?;
857
858			// 1. Load wallet
859			let keypair =
860				crate::wallet::load_keypair_from_wallet(&wallet, password, password_file)?;
861			log_verbose!("🔐 Using wallet: {}", wallet.bright_green());
862
863			// 2. Encode the value based on the --type flag
864			let value_bytes = match r#type.as_deref() {
865				Some("u64") | Some("moment") => value
866					.parse::<u64>()
867					.map_err(|e| QuantusError::Generic(format!("Invalid u64 value: {e}")))?
868					.encode(),
869				Some("u128") | Some("balance") => value
870					.parse::<u128>()
871					.map_err(|e| QuantusError::Generic(format!("Invalid u128 value: {e}")))?
872					.encode(),
873				Some("accountid") | Some("accountid32") => AccountId32::from_ss58check(&value)
874					.map_err(|e| QuantusError::Generic(format!("Invalid AccountId value: {e:?}")))?
875					.encode(),
876				None => {
877					// Default to hex decoding if no type is specified
878					// Try to parse as H256 first, then fall back to hex decode
879					if value.starts_with("0x") && value.len() == 66 {
880						// 0x + 64 hex chars = 66 (32 bytes)
881						// Try to parse as H256
882						let h256_value = subxt::utils::H256::from_str(&value).map_err(|e| {
883							QuantusError::Generic(format!("Invalid H256 value: {e}"))
884						})?;
885						h256_value.0.to_vec()
886					} else {
887						// Fall back to hex decode for other hex values
888						let value_hex = value.strip_prefix("0x").unwrap_or(&value);
889						hex::decode(value_hex)
890							.map_err(|e| QuantusError::Generic(format!("Invalid hex value: {e}")))?
891					}
892				},
893				Some(unsupported) =>
894					return Err(QuantusError::Generic(format!(
895						"Unsupported type for --type: {unsupported}"
896					))),
897			};
898
899			log_verbose!("Encoded value bytes: 0x{}", hex::encode(&value_bytes).dimmed());
900
901			// 3. Construct the storage key
902			let storage_key = {
903				let mut key = twox_128(pallet.as_bytes()).to_vec();
904				key.extend(&twox_128(name.as_bytes()));
905				key
906			};
907
908			// 4. Submit the set storage transaction
909			let tx_hash =
910				set_storage_value(&quantus_client, &keypair, storage_key, value_bytes, finalized)
911					.await?;
912
913			log_print!(
914				"✅ {} Set storage transaction submitted! Hash: {:?}",
915				"SUCCESS".bright_green().bold(),
916				tx_hash
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}