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							},
615							Err(_) => log_print!(
616								"   Value: 0x{} (raw)",
617								hex::encode(&value_bytes).dimmed()
618							),
619						}
620					}
621				}
622			}
623		}
624	}
625
626	if total_count > limit {
627		log_print!("");
628		log_print!(
629			"... and {} more entries (use --limit 0 to just count)",
630			(total_count - limit).to_string().bright_blue()
631		);
632	}
633
634	Ok(())
635}
636
637/// Decode storage value based on type
638fn decode_storage_value(value_bytes: &[u8], type_str: &str) -> crate::error::Result<String> {
639	match type_str.to_lowercase().as_str() {
640		"u32" => match u32::decode(&mut &value_bytes[..]) {
641			Ok(decoded_value) => Ok(decoded_value.to_string()),
642			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u32: {e}"))),
643		},
644		"u64" | "moment" => match u64::decode(&mut &value_bytes[..]) {
645			Ok(decoded_value) => Ok(decoded_value.to_string()),
646			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u64: {e}"))),
647		},
648		"u128" | "balance" => match u128::decode(&mut &value_bytes[..]) {
649			Ok(decoded_value) => Ok(decoded_value.to_string()),
650			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as u128: {e}"))),
651		},
652		"accountid" | "accountid32" => match AccountId32::decode(&mut &value_bytes[..]) {
653			Ok(account_id) => Ok(account_id.to_quantus_ss58()),
654			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as AccountId32: {e}"))),
655		},
656		"accountinfo" => match AccountInfo::decode(&mut &value_bytes[..]) {
657			Ok(account_info) => Ok(format!("{account_info:#?}")),
658			Err(e) => Err(QuantusError::Generic(format!("Failed to decode as AccountInfo: {e}"))),
659		},
660		_ => Err(QuantusError::Generic(format!(
661			"Unsupported type for decoding: {type_str}. Supported types: u32, u64, moment, u128, balance, accountid, accountinfo"
662		))),
663	}
664}
665
666/// Get storage by a full, hex-encoded storage key.
667async fn get_storage_by_storage_key(
668	quantus_client: &crate::chain::client::QuantusClient,
669	storage_key: String,
670	block: Option<String>,
671	decode_as: Option<String>,
672) -> crate::error::Result<()> {
673	log_print!("🗄️  Storage");
674
675	let encoded_storage_key = encode_storage_key(&storage_key, "raw")?;
676
677	let result = if let Some(block_id) = block {
678		let block_hash = resolve_block_hash(quantus_client, &block_id).await?;
679		get_storage_raw_at_block(quantus_client, encoded_storage_key, block_hash).await?
680	} else {
681		get_storage_raw(quantus_client, encoded_storage_key).await?
682	};
683
684	if let Some(value_bytes) = result {
685		log_success!("Raw Value: 0x{}", hex::encode(&value_bytes).bright_yellow());
686		if let Some(type_str) = decode_as {
687			log_print!("Attempting to decode as {}...", type_str.bright_cyan());
688			match decode_storage_value(&value_bytes, &type_str) {
689				Ok(decoded_value) => {
690					log_success!("Decoded Value: {}", decoded_value.bright_green())
691				},
692				Err(e) => log_error!("{}", e),
693			}
694		}
695	} else {
696		log_print!("{}", "No value found at this storage location.".dimmed());
697	}
698
699	Ok(())
700}
701
702/// Get storage by providing its pallet, name, and optional key component.
703async fn get_storage_by_parts(
704	quantus_client: &crate::chain::client::QuantusClient,
705	pallet: String,
706	name: String,
707	key: Option<String>,
708	key_type: Option<String>,
709	block: Option<String>,
710	decode_as: Option<String>,
711	count: bool,
712) -> crate::error::Result<()> {
713	if let Some(block_value) = &block {
714		log_print!(
715			"🔎 Getting storage for {}::{} at block {}",
716			pallet.bright_green(),
717			name.bright_cyan(),
718			block_value.bright_yellow()
719		);
720	} else {
721		log_print!(
722			"🔎 Getting storage for {}::{} (latest block)",
723			pallet.bright_green(),
724			name.bright_cyan()
725		);
726	}
727
728	if let Some(key_value) = &key {
729		log_print!("🔑 With key: {}", key_value.bright_yellow());
730	}
731
732	validate_pallet_exists(quantus_client.client(), &pallet)?;
733
734	let block_hash = if let Some(block_id) = &block {
735		resolve_block_hash(quantus_client, block_id).await?
736	} else {
737		quantus_client.get_latest_block().await?
738	};
739
740	let entry_count = count_storage_entries(quantus_client, &pallet, &name, block_hash).await?;
741	let is_storage_value = entry_count == 1;
742
743	let should_count = count || (key.is_none() && !is_storage_value);
744
745	if should_count {
746		log_print!("🔢 Counting all entries in {}::{}", pallet.bright_green(), name.bright_cyan());
747
748		let block_display = if let Some(ref block_id) = block {
749			format!(" at block {}", block_id.bright_yellow())
750		} else {
751			" (latest)".to_string()
752		};
753
754		log_success!(
755			"👥 Total entries{}: {}",
756			block_display,
757			entry_count.to_string().bright_green().bold()
758		);
759	} else {
760		let mut storage_key = twox_128(pallet.as_bytes()).to_vec();
761		storage_key.extend(&twox_128(name.as_bytes()));
762
763		if let Some(key_value) = &key {
764			if let Some(key_type_str) = &key_type {
765				let key_bytes = encode_storage_key(key_value, key_type_str)?;
766				storage_key.extend(key_bytes);
767			} else {
768				log_error!("Key type (--key-type) is required when using --key parameter");
769				return Ok(());
770			}
771		} else if !is_storage_value {
772			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);
773			return Ok(());
774		}
775
776		let result = get_storage_raw_at_block(quantus_client, storage_key, block_hash).await?;
777
778		if let Some(value_bytes) = result {
779			log_success!("Raw Value: 0x{}", hex::encode(&value_bytes).bright_yellow());
780
781			if let Some(type_str) = decode_as {
782				log_print!("Attempting to decode as {}...", type_str.bright_cyan());
783				match decode_storage_value(&value_bytes, &type_str) {
784					Ok(decoded_value) => {
785						log_success!("Decoded Value: {}", decoded_value.bright_green())
786					},
787					Err(e) => log_error!("{}", e),
788				}
789			}
790		} else {
791			log_print!("{}", "No value found at this storage location.".dimmed());
792		}
793	}
794
795	Ok(())
796}
797
798/// Handle storage subxt commands
799pub async fn handle_storage_command(
800	command: StorageCommands,
801	node_url: &str,
802) -> crate::error::Result<()> {
803	log_print!("🗄️  Storage");
804
805	let quantus_client = crate::chain::client::QuantusClient::new(node_url).await?;
806
807	match command {
808		StorageCommands::Get {
809			pallet,
810			name,
811			block,
812			decode_as,
813			key,
814			key_type,
815			count,
816			storage_key,
817		} => {
818			if let Some(s_key) = storage_key {
819				get_storage_by_storage_key(&quantus_client, s_key, block, decode_as).await
820			} else {
821				// Clap ensures that pallet and name are present if `storage_key` is None
822				get_storage_by_parts(
823					&quantus_client,
824					pallet.unwrap(),
825					name.unwrap(),
826					key,
827					key_type,
828					block,
829					decode_as,
830					count,
831				)
832				.await
833			}
834		},
835		StorageCommands::List { pallet, names_only } =>
836			list_storage_items(&quantus_client, &pallet, names_only).await,
837		StorageCommands::ListPallets { with_counts } =>
838			list_pallets_with_storage(&quantus_client, with_counts).await,
839		StorageCommands::Stats { pallet, detailed } =>
840			show_storage_stats(&quantus_client, pallet, detailed).await,
841		StorageCommands::Iterate { pallet, name, limit, decode_as, block } =>
842			iterate_storage_entries(&quantus_client, &pallet, &name, limit, decode_as, block).await,
843
844		StorageCommands::Set { pallet, name, value, wallet, password, password_file, r#type } => {
845			log_print!("✍️  Setting storage for {}::{}", pallet.bright_green(), name.bright_cyan());
846			log_print!("\n{}", "🛑 This is a SUDO operation!".bright_red().bold());
847
848			// Validate pallet exists
849			validate_pallet_exists(quantus_client.client(), &pallet)?;
850
851			// 1. Load wallet
852			let keypair =
853				crate::wallet::load_keypair_from_wallet(&wallet, password, password_file)?;
854			log_verbose!("🔐 Using wallet: {}", wallet.bright_green());
855
856			// 2. Encode the value based on the --type flag
857			let value_bytes = match r#type.as_deref() {
858				Some("u64") | Some("moment") => value
859					.parse::<u64>()
860					.map_err(|e| QuantusError::Generic(format!("Invalid u64 value: {e}")))?
861					.encode(),
862				Some("u128") | Some("balance") => value
863					.parse::<u128>()
864					.map_err(|e| QuantusError::Generic(format!("Invalid u128 value: {e}")))?
865					.encode(),
866				Some("accountid") | Some("accountid32") => AccountId32::from_ss58check(&value)
867					.map_err(|e| QuantusError::Generic(format!("Invalid AccountId value: {e:?}")))?
868					.encode(),
869				None => {
870					// Default to hex decoding if no type is specified
871					// Try to parse as H256 first, then fall back to hex decode
872					if value.starts_with("0x") && value.len() == 66 {
873						// 0x + 64 hex chars = 66 (32 bytes)
874						// Try to parse as H256
875						let h256_value = subxt::utils::H256::from_str(&value).map_err(|e| {
876							QuantusError::Generic(format!("Invalid H256 value: {e}"))
877						})?;
878						h256_value.0.to_vec()
879					} else {
880						// Fall back to hex decode for other hex values
881						let value_hex = value.strip_prefix("0x").unwrap_or(&value);
882						hex::decode(value_hex)
883							.map_err(|e| QuantusError::Generic(format!("Invalid hex value: {e}")))?
884					}
885				},
886				Some(unsupported) =>
887					return Err(QuantusError::Generic(format!(
888						"Unsupported type for --type: {unsupported}"
889					))),
890			};
891
892			log_verbose!("Encoded value bytes: 0x{}", hex::encode(&value_bytes).dimmed());
893
894			// 3. Construct the storage key
895			let storage_key = {
896				let mut key = twox_128(pallet.as_bytes()).to_vec();
897				key.extend(&twox_128(name.as_bytes()));
898				key
899			};
900
901			// 4. Submit the set storage transaction
902			let tx_hash =
903				set_storage_value(&quantus_client, &keypair, storage_key, value_bytes).await?;
904
905			log_print!(
906				"✅ {} Set storage transaction submitted! Hash: {:?}",
907				"SUCCESS".bright_green().bold(),
908				tx_hash
909			);
910
911			let success = wait_for_tx_confirmation(quantus_client.client(), tx_hash).await?;
912
913			if success {
914				log_success!(
915					"🎉 {} Set storage transaction confirmed!",
916					"FINISHED".bright_green().bold()
917				);
918			} else {
919				log_error!("Transaction failed!");
920			}
921
922			Ok(())
923		},
924	}
925}
926
927/// Encode storage key parameter based on type
928fn encode_storage_key(key_value: &str, key_type: &str) -> crate::error::Result<Vec<u8>> {
929	use codec::Encode;
930	use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58Codec};
931
932	match key_type.to_lowercase().as_str() {
933		"accountid" | "accountid32" => {
934			let account_id = SpAccountId32::from_ss58check(key_value).map_err(|e| {
935				crate::error::QuantusError::Generic(format!("Invalid AccountId: {e:?}"))
936			})?;
937			Ok(account_id.encode())
938		},
939		"u64" => {
940			let value = key_value
941				.parse::<u64>()
942				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid u64: {e}")))?;
943			Ok(value.encode())
944		},
945		"u128" => {
946			let value = key_value
947				.parse::<u128>()
948				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid u128: {e}")))?;
949			Ok(value.encode())
950		},
951		"u32" => {
952			let value = key_value
953				.parse::<u32>()
954				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid u32: {e}")))?;
955			Ok(value.encode())
956		},
957		"hex" | "raw" => {
958			// For hex/raw keys, decode the hex string directly
959			let value_hex = key_value.strip_prefix("0x").unwrap_or(key_value);
960			hex::decode(value_hex)
961				.map_err(|e| crate::error::QuantusError::Generic(format!("Invalid hex value: {e}")))
962		},
963		_ => Err(crate::error::QuantusError::Generic(format!(
964			"Unsupported key type: {key_type}. Supported types: accountid, u64, u128, u32, hex, raw"
965		))),
966	}
967}