# Historical accounts (/solana-rpc/historical-accounts)

> Read Solana account state at any past slot back to genesis, and step through an account's change history with getAccountInfo.



You can read any account as it was at a past slot, all the way back to genesis. These methods take a `slot` in their config, and `getAccountInfo` can also walk through an account's history:

| Method                                     | `slot` | History cursors | Config parameter                            |
| ------------------------------------------ | :----: | :-------------: | ------------------------------------------- |
| `getAccountInfo`                           |   Yes  |       Yes       | 2nd                                         |
| `getBalance`                               |   Yes  |        No       | 2nd                                         |
| `getMultipleAccounts` (up to 100 accounts) |   Yes  |        No       | 2nd                                         |
| `getProgramAccounts`                       |   Yes  |        No       | 2nd                                         |
| `getTokenAccountsByOwner`                  |   Yes  |        No       | 3rd, after the `mint` or `programId` filter |

## Read an account at a slot

```bash
curl --fail-with-body "$CORVUS_RPC_URL" \
  --header 'content-type: application/json' \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getAccountInfo",
    "params": [
      "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      { "encoding": "jsonParsed", "slot": 400000000 }
    ]
  }'
```

You get the account as it was at the end of that slot, and `context.slot` is the slot you asked for. If the account didn't exist yet or had been closed, `value` is `null`. Encodings, `dataSlice` and `getProgramAccounts` filters work as usual.

## Walk an account's history

`getAccountInfo` also takes two cursors that jump straight to the slots where the account changed:

| Parameter              | Returns                          |
| ---------------------- | -------------------------------- |
| `lastUpdateBeforeSlot` | The last change before that slot |
| `firstUpdateAfterSlot` | The first change after that slot |

`context.slot` in the response is the slot of that change, so pass it back as the next cursor. `value` is the account right after the change, or `null` if the change closed it. When there are no more changes you get error `-32020` (`Out of coverage`), and the walk is done.

```ts
// Every version of an account, newest first.
async function* history(rpcUrl: string, account: string, fromSlot: number) {
  let cursor = fromSlot;
  while (true) {
    const res = await fetch(rpcUrl, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 1,
        method: 'getAccountInfo',
        params: [account, { encoding: 'base64', lastUpdateBeforeSlot: cursor }],
      }),
    });
    const { result, error } = await res.json();
    if (error?.code === -32020) return;
    if (error) throw new Error(error.message);
    yield { slot: result.context.slot, account: result.value };
    cursor = result.context.slot;
  }
}
```

The cursors only work with `getAccountInfo`, one at a time, and can't be combined with `slot`. They're the same parameters Alchemy's Account Archive uses, so code written for it works here too.

## How far back

History starts at genesis. The newest slots, up to about an hour and a half behind the tip, aren't available yet; for those, leave `slot` out and read the current state.

Historical calls count against your RPS like any other call; see [Rate limits](/solana-rpc/limits).

## Latency

The archive is in Amsterdam, so use the `ams` endpoint for historical calls. From other regions, add the network distance to Amsterdam.

Time spent in the archive, before any network time:

| Method                |     p50 |     p90 |
| --------------------- | ------: | ------: |
| `getAccountInfo`      | 0.04 ms | 0.19 ms |
| `getMultipleAccounts` | 1.34 ms | 2.42 ms |
| `getProgramAccounts`  | 0.33 ms | 0.66 ms |

## Errors

|     Code | When                                                                                                                                                             |
| -------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-32080` | The slot is outside the covered range. The error's `data` has `firstSlot` and `lastSlot`.                                                                        |
| `-32020` | `Out of coverage`: there are no more changes in that direction.                                                                                                  |
| `-32602` | Invalid parameters, such as a cursor on a method other than `getAccountInfo`, `slot` together with a cursor, or more than 100 accounts in `getMultipleAccounts`. |
