# state_queryStorageAt - Kusama RPC Method

Queries multiple storage keys at a specific block on Kusama, returning all values in a single call. This is the preferred method for fetching consistent multi-key state snapshots, as all values are read from the same block.

> **Why Kusama?** Build on Polkadot's canary network for real-world testing with live economic conditions with 7-day governance cycles (vs 1 month on Polkadot), lower bonding requirements, live KSM token economy, and first-to-market feature testing.

## When to Use This Method

`state_queryStorageAt` is essential for experimental dApp developers, parachain teams, and early adopters validating new features:

- **Consistent State Snapshots** -- Fetch multiple storage values from the same block to ensure data consistency on Kusama
- **Batch Raw Storage Reads** -- Retrieve several known storage keys in one RPC call
- **Indexer and Analytics** -- Build efficient data pipelines by querying all required storage keys at once
- **Historical State Analysis** -- Compare storage state across different blocks for auditing and data analysis

## Best Practices

- Requires an archive node for querying deep historical state
- More efficient than making individual `state_getStorage` calls for multiple keys
- Accepts multiple storage keys in a single request for batch retrieval
- Use block hashes (not numbers) for deterministic historical queries

## Request Parameters

- `keys` (`Array<String>, required`): Array of hex-encoded storage keys to query
- `blockHash` (`String, optional`): Block hash to query at; defaults to the best block if omitted

## Request Example

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

## Response Fields

- `block` (`String, required`): The block hash at which the query was executed
- `changes` (`Array<[String, String|null]>, required`): Array of [key, value] pairs. The value is a hex-encoded SCALE value, or null if the key does not exist

## Successful Response

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "block": "0x1a2b3c4d5e6f...",
      "changes": [
        [
          "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
          "0x0100000000000000010000000000000000407a10f35a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
        ]
      ]
    }
  ]
}
```

## Error Responses

### Error Response

- Code: `-32603`

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32603,
    "message": "Internal error"
  }
}
```

## Code Examples

cURL
JavaScript
Python
Rust

```bash
curl -X POST https://kusama-rpc.n.dwellir.com \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "state_queryStorageAt",
    "params": [
      [
        "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"
      ]
    ],
    "id": 1
  }'
```

```javascript
// Using @polkadot/api helpers to construct storage keys
import { ApiPromise, WsProvider } from '@polkadot/api';

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

// High-level: query multiple accounts at once
const accounts = [
  '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
  '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
];
const storageKeys = await Promise.all(
  accounts.map((addr) => api.query.system.account.key(addr))
);

const queryResult = await api.rpc.state.queryStorageAt(storageKeys);
console.log('Block:', queryResult[0].block.toHex());
console.log('Changes:', queryResult[0].changes.length);

// Metadata-aware alternative: decode those same accounts at the latest state
const decoded = await api.query.system.account.multi(accounts);
decoded.forEach((info, idx) => {
  console.log(`Decoded account ${accounts[idx]} free balance:`, info.data.free.toString());
});

await api.disconnect();

// Using fetch (HTTP JSON-RPC)
const response = await fetch('https://kusama-rpc.n.dwellir.com', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'state_queryStorageAt',
    params: [storageKeys.map((k) => k.toHex())],
    id: 1
  })
});

const { result } = await response.json();
console.log('Queried at block:', result[0].block);
```

```python
import requests

def query_storage_at(keys, block_hash=None):
    params = [keys]
    if block_hash:
        params.append(block_hash)

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

# Query System.Account storage for an account
storage_key = '0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'
result = query_storage_at([storage_key])
print(f"Block: {result[0]['block']}")
for key, value in result[0]['changes']:
    print(f"  Key: {key[:40]}...")
    print(f"  Value: {value[:40] if value else 'null'}...")

# Using substrate-interface
from substrateinterface import SubstrateInterface

substrate = SubstrateInterface(url='wss://kusama-rpc.n.dwellir.com')
result = substrate.rpc_request('state_queryStorageAt', [[storage_key]])['result']
print(f"Changes: {len(result[0]['changes'])}")
```

```rust
use serde_json::json;

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

    let storage_key = "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d";

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

    let result: serde_json::Value = response.json().await?;
    let entries = &result["result"][0];

    println!("Block: {}", entries["block"]);
    if let Some(changes) = entries["changes"].as_array() {
        for change in changes {
            let key = change[0].as_str().unwrap_or("");
            let value = change[1].as_str().unwrap_or("null");
            println!("  Key: {}...", &key[..std::cmp::min(40, key.len())]);
            println!("  Value: {}...", &value[..std::cmp::min(40, value.len())]);
        }
    }

    Ok(())
}
```

## Common Use Cases

### 1. Multi-Key Snapshot

Read multiple storage keys from the same block:

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

async function getStorageSnapshot(api, addresses) {
  const keys = await Promise.all(addresses.map((address) => api.query.system.account.key(address)));
  const results = await api.rpc.state.queryStorageAt(keys);

  return results[0].changes.map(([key, value], idx) => ({
    address: addresses[idx],
    key: key.toHex(),
    raw: value?.toHex() ?? null
  }));
}

const snapshot = await getStorageSnapshot(api, [
  '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
  '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'
]);

snapshot.forEach((entry) => {
  console.log(`${entry.address}: ${entry.raw}`);
});
```

### 2. Historical State Comparison

Compare storage state between two blocks for auditing:

```javascript
async function compareStorageAtBlocks(api, keys, blockHash1, blockHash2) {
  const [result1, result2] = await Promise.all([
    api.rpc.state.queryStorageAt(keys, blockHash1),
    api.rpc.state.queryStorageAt(keys, blockHash2)
  ]);

  const changes1 = new Map(result1[0].changes.map(([k, v]) => [k.toHex(), v?.toHex()]));
  const changes2 = new Map(result2[0].changes.map(([k, v]) => [k.toHex(), v?.toHex()]));

  const diffs = [];
  for (const [key, val1] of changes1) {
    const val2 = changes2.get(key);
    if (val1 !== val2) {
      diffs.push({ key, before: val1, after: val2 });
    }
  }

  console.log(`Found ${diffs.length} storage changes between blocks`);
  return diffs;
}
```

### 3. Efficient Indexer State Fetching

Fetch all required storage in a single batch for indexer pipelines:

```javascript
async function fetchBlockState(api, blockHash) {
  // Build storage keys for multiple storage items
  const keys = [
    api.query.system.number.key(),              // block number
    api.query.timestamp.now.key(),               // timestamp
    api.query.system.eventCount.key(),           // event count
    api.query.system.extrinsicCount.key()        // extrinsic count
  ];

  const result = await api.rpc.state.queryStorageAt(keys, blockHash);
  const changes = new Map(
    result[0].changes.map(([k, v]) => [k.toHex(), v?.toHex()])
  );

  return {
    block: blockHash,
    keyCount: changes.size,
    entries: Object.fromEntries(changes)
  };
}
```

## Related Methods

- [`state_getStorage`](https://www.dwellir.com/docs/kusama/state_getStorage) -- Query a single storage key
- [`state_getKeysPaged`](https://www.dwellir.com/docs/kusama/state_getKeysPaged) -- Enumerate storage keys with pagination
- [`state_call`](https://www.dwellir.com/docs/kusama/state_call) -- Call a runtime API function
- [`state_getMetadata`](https://www.dwellir.com/docs/kusama/state_getMetadata) -- Get runtime metadata to construct storage keys
- [`chain_getBlockHash`](https://www.dwellir.com/docs/kusama/chain_getBlockHash) -- Get a block hash by block number for historical queries
