Corvus Labs

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:

MethodslotHistory cursorsConfig parameter
getAccountInfoYesYes2nd
getBalanceYesNo2nd
getMultipleAccounts (up to 100 accounts)YesNo2nd
getProgramAccountsYesNo2nd
getTokenAccountsByOwnerYesNo3rd, after the mint or programId filter

Read an account at a slot

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:

ParameterReturns
lastUpdateBeforeSlotThe last change before that slot
firstUpdateAfterSlotThe 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.

// 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.

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:

Methodp50p90
getAccountInfo0.04 ms0.19 ms
getMultipleAccounts1.34 ms2.42 ms
getProgramAccounts0.33 ms0.66 ms

Errors

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

On this page