> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Solana Historical Account API (Beta)

> Read Solana account state as it was at any past slot or transaction with Helius getHistoricalAccountInfo and getHistoricalTokenAccountsByOwner (beta).

> [!NOTE]
>
> Historical account state is in Beta. Parameters, pricing, response formats, and data coverage may change.

## What is historical account state?

Standard Solana RPC methods like `getAccountInfo` and `getTokenAccountsByOwner` return an account's current state only. Validators keep just the latest version of each account, so every write replaces the one before it. Transaction history doesn't fill the gap: `getTransaction` and `getBlock` return instructions, logs, and balance changes, not the account data a program wrote.

Helius's historical account methods return accounts as they were at a past slot, or immediately after a specific transaction within that slot. Because you can read state between two transactions in the same slot, you can see exactly what one transaction changed, even when an account is written many times in a single slot. Use them to:

* Check a wallet's token balances at a point in time, for audits, accounting, or tax reporting
* Debug a transaction by reading the accounts it touched right before and right after it executed
* Read a protocol's accounts, such as pools, vaults, or oracles, at a past slot without replaying the ledger yourself

These are Helius-exclusive RPC methods. They're not part of standard Solana RPC.

## Which method should I use?

| What you need | Use this | Returns |
| - | - | - |
| One account at a past position | [`getHistoricalAccountInfo`](https://www.helius.dev/docs/api-reference/rpc/http/gethistoricalaccountinfo) | The account, or `null` if it didn't exist |
| A wallet's token accounts at a past position | [`getHistoricalTokenAccountsByOwner`](https://www.helius.dev/docs/api-reference/rpc/http/gethistoricaltokenaccountsbyowner) | A page of SPL Token and Token-2022 accounts, optionally for one mint |

Both take the same three parameters: an address, a query object that selects the position in history, and a configuration object.

## Availability and pricing

* **Plans**: Business [plans](https://www.helius.dev/docs/billing/plans.md) and above.
* **Cost**: 0 credits per call during beta (pricing subject to change).
* **Rate limits**: `getHistoricalAccountInfo` allows 100 requests per second, with a maximum burst of 100. `getHistoricalTokenAccountsByOwner` allows 10 requests per second, with a maximum burst of 10. These limits are separate from your plan's RPC rate limit, and requests over them return HTTP `429`. See [rate limits](https://www.helius.dev/docs/billing/rate-limits.md).
* **Network**: Mainnet only. Call them on the standard mainnet endpoint, `https://mainnet.helius-rpc.com`, or the [Gatekeeper](https://www.helius.dev/docs/gatekeeper/overview.md) endpoint, `https://beta.helius-rpc.com`. Devnet returns error `-32601`.

## Data coverage

History is available from slot 444,959,999 (September 7, 2026) onward. That's the last slot of epoch 1029, so it holds the state at the start of epoch 1030. Requests for earlier slots return error `-32018` ("Account history not available").

## Choose a position in history

The second parameter is a query object with two optional fields, `slot` and `transactionIndex`:

| Query | Returns state |
| - | - |
| Omitted or `{}` | At the latest indexed slot, capped at the requested commitment |
| `{ "slot": 445000000 }` | At the end of slot 445,000,000, after every transaction and any non-transaction writes such as epoch rewards |
| `{ "slot": 445000000, "transactionIndex": 10 }` | Immediately after the transaction at index 10 in slot 445,000,000 executed |

`transactionIndex` requires `slot`.

Non-transaction writes, such as epoch rewards, are applied at the end of the slot. A query with `transactionIndex` never includes them, even for the last transaction in the slot. To include them, query the `slot` with no `transactionIndex`.

### Read state before and after a transaction

To inspect what a transaction changed, read the account twice:

* **After the transaction**: query its own `slot` and `transactionIndex`.
* **Before the transaction**: query the same `slot` with `transactionIndex` minus 1. If the transaction is the first in its slot (index 0), query the previous slot with no `transactionIndex`.

[`getTransactionsForAddress`](https://www.helius.dev/docs/rpc/gettransactionsforaddress) returns `slot` and `transactionIndex` for every transaction, so you can find a transaction's position there and pass it straight to these methods.

```bash theme={"system"}
# State of the account right after the transaction at index 10 in slot 445000000
curl https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "getHistoricalAccountInfo",
    "params": [
      "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      { "slot": 445000000, "transactionIndex": 10 },
      { "encoding": "jsonParsed" }
    ]
  }'
```

## Understand lastUpdatedAt

Every returned account includes `lastUpdatedAt`, the position of the write that produced the state you received:

```json theme={"system"}
"lastUpdatedAt": { "slot": 444999837, "transactionIndex": 700 }
```

* **`slot` and `transactionIndex`** point to the last write at or before the position you queried. In the example above, a query for slot 445,000,000 returned state last written by the transaction at index 700 in slot 444,999,837.
* **`transactionIndex: null`** means the write happened outside a transaction, such as epoch rewards.
* **`slot: 444959999` with `transactionIndex: null`** means the account hasn't changed since history coverage began. It doesn't mean the account was written in that slot.

## What a null value means

`value` is `null` when the account didn't exist at the position you queried. Either it hadn't been created yet, or it had been closed.

* **A `null` response has no `lastUpdatedAt`**, so it doesn't tell you when the account was created or closed. To find those transactions, use [`getTransactionsForAddress`](https://www.helius.dev/docs/rpc/gettransactionsforaddress).
* **An address that never existed** also returns `null`, not an error.
* **A slot before history coverage** returns error `-32018` for every address, whether or not the account existed.

## Walk back through account history

Use `lastUpdatedAt` as a cursor to list every state an account has had, newest first. After each read, query the position just before the write you received:

| `lastUpdatedAt` you received | Query next |
| - | - |
| `{ "slot": S, "transactionIndex": N }`, where N is greater than 0 | `{ "slot": S, "transactionIndex": N - 1 }` |
| `{ "slot": S, "transactionIndex": 0 }` | `{ "slot": S - 1 }` |
| `{ "slot": S, "transactionIndex": null }` | `{ "slot": S - 1 }` |

Stop when `value` is `null`, which means the write you just stepped past created the account. Also stop when `lastUpdatedAt.slot` reaches 444,959,999, the start of history coverage.

```typescript theme={"system"}
const url = `https://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`;
const COVERAGE_START_SLOT = 444959999;

type Position = { slot?: number; transactionIndex?: number };

async function getAccountHistory(address: string, start: Position = {}, maxStates = 100) {
  const states = [];
  let position = start;

  while (states.length < maxStates) {
    const response = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: "1",
        method: "getHistoricalAccountInfo",
        params: [address, position, { encoding: "base64" }],
      }),
    });
    const { result, error } = await response.json();
    if (error) throw new Error(`${error.code}: ${error.message}`);

    const account = result.value;
    if (!account) break; // The account didn't exist before this write

    states.push(account);
    const { slot, transactionIndex } = account.lastUpdatedAt;
    if (slot <= COVERAGE_START_SLOT) break; // Reached the start of history coverage

    // Step to the position just before this write
    position = transactionIndex ? { slot, transactionIndex: transactionIndex - 1 } : { slot: slot - 1 };
  }

  return states; // Newest first
}
```

* **Accounts that are closed now** return `null` at the latest slot. Pass a `start` position from when the account still existed.
* **Non-transaction writes** step to the previous slot. If the same account was also written by a transaction earlier in that slot, the walk skips that state.
* **To walk forward in time**, list the account's transactions oldest first with [`getTransactionsForAddress`](https://www.helius.dev/docs/rpc/gettransactionsforaddress) and `sortOrder: "asc"`. Then read the state at each transaction's `slot` and `transactionIndex`.

## Response context

`context.slot` in every response is the latest slot Helius had indexed when it served the request. It isn't the slot you queried. Use the `slot` in your request, or `lastUpdatedAt`, to reason about the data you received.

## Paginate consistently

`getHistoricalTokenAccountsByOwner` returns results one page at a time. Each response includes `value.accounts` and `value.paginationKey`.

* **Pass `paginationKey` back** in the configuration object to fetch the next page.
* **Stop only when `paginationKey` is `null`.** A page can be short, or empty, and still have more results behind it.
* **Pin `slot` across every page.** If you omit `slot`, each page reads the latest indexed slot, and your pages can come from different points in time.
* **Set `limit`** between 1 and 5,000. It defaults to 5,000.

## Encodings and parsed data

* **Supported encodings** are `base64` (the default), `base64+zstd`, and `jsonParsed`. `base58` and `binary` aren't supported.
* **`jsonParsed` token accounts** use the mint's decimals as of the queried position, so amounts are correct even if the mint has since changed. If the mint can't be read at that position, the account falls back to base64.
* **`dataSlice`** works the same way as in `getAccountInfo`.

## Commitment

`commitment` accepts `finalized` (the default) and `confirmed`. `processed` isn't supported. A `slot` newer than the requested commitment allows returns error `-32004`.

## Errors

| Code | Meaning | What to do |
| - | - | - |
| `-32018` | The slot is before history coverage begins | Query slot 444,959,999 or later |
| `-32004` | The slot is newer than the requested commitment allows | Use `confirmed`, or query an older slot |
| `-32602` | Invalid parameters | Check the error message for the specific field |
| `-32601` | Method not found | Call your Helius mainnet or Gatekeeper endpoint. Historical account methods aren't available on devnet |

## Frequently asked questions

### How do I get a Solana account's state at a specific slot?

Call `getHistoricalAccountInfo` with the address and `{ "slot": S }`. It returns the account as it was at the end of slot S, in the same format as `getAccountInfo`.

### Can I read an account between two transactions in the same slot?

Yes. Add `transactionIndex` to the query to get the account's state immediately after that transaction executed. See [Read state before and after a transaction](#read-state-before-and-after-a-transaction).

### How far back can I query?

History starts at slot 444,959,999, on September 7, 2026. Earlier slots return error `-32018`.

### Which plans include historical account methods?

Business plans and above. Calls cost 0 credits during the beta period; prices are subject to change. The methods are available on mainnet only.

### What are the rate limits?

`getHistoricalAccountInfo` allows 100 requests per second, and `getHistoricalTokenAccountsByOwner` allows 10 requests per second. Both limits are separate from your plan's RPC rate limit. See [Availability and pricing](#availability-and-pricing).

### How do I get every past state of an account?

Step backward from one write to the previous one using `lastUpdatedAt`. See [Walk back through account history](#walk-back-through-account-history).

### Do I need a new SDK?

No. These are JSON-RPC methods, so any HTTP client works. Standard Solana client libraries don't include them, so send the request directly, as in the examples above.

## Next steps

> [card] [getHistoricalAccountInfo](https://www.helius.dev/docs/api-reference/rpc/http/gethistoricalaccountinfo.md)
>
> Read a single account at a past slot or transaction

> [card] [getHistoricalTokenAccountsByOwner](https://www.helius.dev/docs/api-reference/rpc/http/gethistoricaltokenaccountsbyowner.md)
>
> List a wallet's token accounts at a past slot

> [card] [getTransactionsForAddress](https://www.helius.dev/docs/rpc/gettransactionsforaddress.md)
>
> Find the slot and transactionIndex of the transactions you want to inspect

> [card] [Historical data overview](https://www.helius.dev/docs/rpc/historical-data.md)
>
> Transaction history, transfers, and other archival methods


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.