# state_getStorage - Enjin RPC Method

Returns the SCALE-encoded storage value for a given key on Enjin. Storage keys are constructed by hashing the pallet name and storage item name (and any map keys) using the hashing algorithms specified in the runtime metadata. This is the fundamental method for reading any on-chain state.

> **Why Enjin?** Build on the purpose-built NFT blockchain with protocol-level minting and $100M Metaverse Fund with NFT functions at protocol level, Fuel Tanks for subsidized fees, 700-1000 TPS, 6-second finality, and ERC-1155 standard pioneer.

## When to Use This Method

`state_getStorage` is essential for game developers, NFT creators, and enterprises building cross-chain digital assets:

- **Low-Level State Access** -- Read the raw SCALE-encoded value stored under a known key on Enjin
- **Metadata-Aware Tooling** -- Pair runtime metadata with raw storage reads when building custom indexers, explorers, or debugging tools
- **Historical State Queries** -- Read storage values at a specific block hash to analyze state changes over time
- **Pallet Storage Inspection** -- Inspect pallet state directly when higher-level client helpers are unavailable or too opinionated

## Best Practices

- Storage keys use pallet-specific encoding -- use `state_getMetadata` to discover key formats
- Handle `null` return values for storage keys that have never been set
- For batch storage reads, use `state_queryStorageAt` for better efficiency
- Cache storage values if querying the same key at the same block height

## Request Parameters

- `key` (`String, required`): Hex-encoded storage key (constructed from pallet name, storage item name, and optional map keys using the appropriate hashing algorithm)
- `blockHash` (`String, optional`): Block hash at which to query storage; defaults to the best block if omitted

## Request Example

```json
{
  "jsonrpc": "2.0",
  "method": "state_getStorage",
  "params": ["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"],
  "id": 1
}
```

## Response Fields

- `result` (`String | null, required`): Hex-encoded SCALE value at the storage key, or null if no value exists at that key

## Successful Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x0000000000000000010000000000000000407a10f35a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080"
}
```

## Error Responses

### Error Response

- Code: `-32603`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32603,
    "message": "Internal error: State not available for block"
  }
}
```

## Code Examples

cURL
JavaScript
Python
Rust

```bash
# Query the System.Account storage for an account
curl -X POST https://enjin-matrix-rpc.n.dwellir.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "state_getStorage",
    "params": ["0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"],
    "id": 1
  }'

# Query at a specific block hash
# Replace 0xYOUR_RECENT_BLOCK_HASH with a recent finalized hash from chain_getFinalizedHead
curl -X POST https://enjin-matrix-rpc.n.dwellir.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "state_getStorage",
    "params": [
      "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
      "0xYOUR_RECENT_BLOCK_HASH"
    ],
    "id": 1
  }'
```

```javascript
// Using @polkadot/api (recommended -- handles key construction and decoding)
import { ApiPromise, WsProvider } from '@polkadot/api';

const provider = new WsProvider('wss://enjin-matrix-rpc.n.dwellir.com');
const api = await ApiPromise.create({ provider });

// Construct a storage key with metadata-aware helpers
const account = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY';
const storageKey = api.query.system.account.key(account);
console.log('Storage key:', storageKey);

// Read the raw SCALE-encoded value with state_getStorage
const rawValue = await api.rpc.state.getStorage(storageKey);
console.log('Raw SCALE value:', rawValue.toHex());

// Historical read at a specific block hash
const blockHash = await api.rpc.chain.getFinalizedHead();
const historicalRaw = await api.rpc.state.getStorage(storageKey, blockHash);
console.log('Historical raw SCALE value:', historicalRaw?.toHex() ?? null);

// Metadata-aware alternative: decode the same key via api.query
const accountInfo = await api.query.system.account(account);
console.log('Decoded free balance:', accountInfo.data.free.toString());

await api.disconnect();

// Using fetch (HTTP JSON-RPC) with a precomputed storage key
const response = await fetch('https://enjin-matrix-rpc.n.dwellir.com', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'state_getStorage',
    params: ['0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9...'],
    id: 1
  })
});

const { result } = await response.json();
console.log('SCALE-encoded storage value:', result);
```

```python
import requests

def get_storage(key, block_hash=None):
    params = [key]
    if block_hash:
        params.append(block_hash)

    response = requests.post(
        'https://enjin-matrix-rpc.n.dwellir.com',
        json={
            'jsonrpc': '2.0',
            'method': 'state_getStorage',
            'params': params,
            'id': 1
        }
    )
    return response.json()['result']

# Query raw storage with a precomputed key
storage_key = '0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9...'
value = get_storage(storage_key)
if value:
    print(f'Storage value: {value[:66]}...')
else:
    print('No value at this key')

# Metadata-aware alternative using substrate-interface
from substrateinterface import SubstrateInterface

substrate = SubstrateInterface(url='wss://enjin-matrix-rpc.n.dwellir.com')

# High-level query with automatic SCALE decoding
result = substrate.query(
    module='System',
    storage_function='Account',
    params=['5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY']
)

print(f"Nonce: {result.value['nonce']}")
print(f"Free: {result.value['data']['free']}")
print(f"Reserved: {result.value['data']['reserved']}")

# Historical query at a specific block
result_at = substrate.query(
    module='System',
    storage_function='Account',
    params=['5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'],
    block_hash=substrate.rpc_request('chain_getFinalizedHead', [])['result']
)
print(f"Historical free: {result_at.value['data']['free']}")
```

```rust
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();

    // Precomputed storage key for System.Account
    let storage_key = "0x26aa394eea5630e07c48ae0c9558cef7\
        b99d880ec681799c0cf30e8886371da9\
        de1e86a9a8c739864cf3cc5ec2bea59f\
        d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d";

    let response = client
        .post("https://enjin-matrix-rpc.n.dwellir.com")
        .json(&json!({
            "jsonrpc": "2.0",
            "method": "state_getStorage",
            "params": [storage_key],
            "id": 1
        }))
        .send()
        .await?;

    let result: serde_json::Value = response.json().await?;

    match result["result"].as_str() {
        Some(value) => {
            println!("SCALE-encoded value: {}", &value[..66.min(value.len())]);
            // Decode using parity-scale-codec or subxt for typed access
        }
        None => println!("No value at this storage key"),
    }

    // Query at a specific block hash
    let block_hash = "0xYOUR_RECENT_BLOCK_HASH";
    let historical = client
        .post("https://enjin-matrix-rpc.n.dwellir.com")
        .json(&json!({
            "jsonrpc": "2.0",
            "method": "state_getStorage",
            "params": [storage_key, block_hash],
            "id": 1
        }))
        .send()
        .await?;

    let hist_result: serde_json::Value = historical.json().await?;
    println!("Historical value: {:?}", hist_result["result"]);

    Ok(())
}
```

## Common Use Cases

### 1. Raw Storage Watcher

Query and track changes for a specific storage key over time:

```javascript
import { ApiPromise, WsProvider } from '@polkadot/api';

async function monitorStorageKey(api, storageKey, intervalMs = 12000) {
  let previousValue = null;

  setInterval(async () => {
    const current = await api.rpc.state.getStorage(storageKey);
    const raw = current?.toHex() ?? null;

    if (previousValue !== null && raw !== previousValue) {
      console.log(`Storage value changed: ${previousValue} -> ${raw}`);
    }

    previousValue = raw;
  }, intervalMs);
}
```

### 2. Metadata-Aware Decode

Use a higher-level library to decode the value after you confirm the raw storage key:

```javascript
async function decodeAccountStorage(api, address) {
  const storageKey = api.query.system.account.key(address);
  const raw = await api.rpc.state.getStorage(storageKey);
  const decoded = await api.query.system.account(address);

  return {
    storageKey: storageKey.toHex(),
    raw: raw?.toHex() ?? null,
    decoded: decoded.toJSON()
  };
}
```

### 3. Historical State Comparison

Compare storage values between two blocks to detect state transitions:

```javascript
async function compareStateAtBlocks(api, storageQuery, params, blockHashA, blockHashB) {
  const [apiAtA, apiAtB] = await Promise.all([
    api.at(blockHashA),
    api.at(blockHashB)
  ]);

  // Navigate the nested query path (e.g., 'system.account')
  const parts = storageQuery.split('.');
  let queryA = apiAtA.query;
  let queryB = apiAtB.query;
  for (const part of parts) {
    queryA = queryA[part];
    queryB = queryB[part];
  }

  const [valueA, valueB] = await Promise.all([
    queryA(...params),
    queryB(...params)
  ]);

  const jsonA = valueA.toJSON();
  const jsonB = valueB.toJSON();

  console.log(`Block A: ${JSON.stringify(jsonA, null, 2)}`);
  console.log(`Block B: ${JSON.stringify(jsonB, null, 2)}`);

  return { before: jsonA, after: jsonB };
}

// Example: compare account state between two blocks
// compareStateAtBlocks(api, 'system.account', ['5GrwvaEF...'], blockHashOld, blockHashNew);
```

## Storage Key Construction

For developers who need to construct storage keys manually (without a high-level library):

| Storage Type   | Key Structure                                                         | Example                                 |
| -------------- | --------------------------------------------------------------------- | --------------------------------------- |
| **Value**      | `xxhash128(Pallet) + xxhash128(Item)`                                 | `Timestamp.Now`                         |
| **Map**        | `xxhash128(Pallet) + xxhash128(Item) + hasher(Key)`                   | `System.Account(accountId)`             |
| **Double Map** | `xxhash128(Pallet) + xxhash128(Item) + hasher1(Key1) + hasher2(Key2)` | `Staking.ErasStakers(era, validatorId)` |

Common hashers used in Substrate:

- **Blake2\_128Concat** -- 16-byte Blake2b hash followed by the raw key (allows key enumeration)
- **Twox64Concat** -- 8-byte xxhash followed by the raw key (faster, for trusted keys)
- **Identity** -- Raw key with no hashing (used for already-unique keys)

## Related Methods

- [`state_getKeysPaged`](https://www.dwellir.com/docs/enjin/state_getKeysPaged) -- Enumerate storage keys matching a prefix (useful for iterating map entries)
- [`state_queryStorageAt`](https://www.dwellir.com/docs/enjin/state_queryStorageAt) -- Query multiple storage keys at a specific block in a single request
- [`state_getMetadata`](https://www.dwellir.com/docs/enjin/state_getMetadata) -- Get runtime metadata including storage definitions, types, and hashing algorithms
- [`state_call`](https://www.dwellir.com/docs/enjin/state_call) -- Call runtime APIs for computed state that is not directly in storage
- `state_subscribeStorage` -- Subscribe to storage changes in real time via WebSocket
