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
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.
// 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:
| 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. |
Solana JSON-RPC over HTTP
Send Solana JSON-RPC 2.0 requests to the Corvus endpoint on port 8899, batch calls, and match gateway error codes correctly.
Solana WebSocket subscriptions
Subscribe to Solana PubSub over the Corvus WebSocket endpoint on port 8900 and rebuild subscriptions correctly after a disconnect.