# Address Lookup Tables

> Address Lookup Table queries through getProgramAccounts and getProgramAccountsV2, answered from an in-memory index, with plan rates, account layout and filters.

Source: https://docs.constant-k.com/rpc/address-lookup-tables/

Address Lookup Tables are found and read in bulk with `getProgramAccounts` or `getProgramAccountsV2` on the Address Lookup Table program.

## Access

Queries go through [`getProgramAccounts`](https://docs.constant-k.com/rpc/http/getprogramaccounts/) or [`getProgramAccountsV2`](https://docs.constant-k.com/rpc/http/getprogramaccountsv2/) with `AddressLookupTab1e1111111111111111111111111` as the first parameter. The endpoint and the API key are the same as every other Solana RPC call. There is no separate method, hostname or add-on.

| Plan                | Availability                                                                                                                                  |
| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- |
| Blade Runner, Nexus | Served                                                                                                                                        |
| Free, Operator      | HTTP `403`, code `-32600`, message `getProgramAccounts requires higher tier`. The `getProgramAccountsV2` message names `getProgramAccountsV2` |

Answers come from an in-memory index, not a disk scan. No filter is required, and the 256 MB scan-results cap of a disk scan does not apply. Batch requests (a JSON array of calls) are not served: one call per HTTP request.

## Rate limits

| Plan           | Calls per second per API key |
| :------------- | :--------------------------: |
| Blade Runner   |             25/s             |
| Nexus          |             75/s             |
| Free, Operator |               —              |

The budget is shared per API key. Address Lookup Table queries count together with every other `getProgramAccounts` and `getProgramAccountsV2` call on that key, whichever program they address. A Nexus key sending 50 calls per second against other programs has 25 calls per second left for table queries.

There is no cap on open requests; only the rate is counted. The plan's total requests-per-second limit also applies. See [Plans & Limits](https://docs.constant-k.com/get-started/plans-and-limits/).

Over the rate, the response is HTTP `429` with code `-32005` and the message `Rate limit exceeded (gpa: 25/sec)` on Blade Runner or `Rate limit exceeded (gpa: 75/sec)` on Nexus. It carries no `Retry-After` header and no rate-limit headers. The window is one whole second. See [Gateway errors](https://docs.constant-k.com/rpc/errors/#gateway-errors).

## One known table

A table whose address is already known is read with [`getAccountInfo`](https://docs.constant-k.com/rpc/http/getaccountinfo/) or [`getMultipleAccounts`](https://docs.constant-k.com/rpc/http/getmultipleaccounts/) with `encoding: "jsonParsed"`. That is a plain account read: it is not an index query and does not count against the `getProgramAccounts` budget.

`getProgramAccounts` on the Address Lookup Table program is for finding tables by content (authority, size, addresses, status) or for walking all tables.

## Account layout

| Offset |   Size | Field                        | Notes                                                                                  |
| :----- | -----: | :--------------------------- | :------------------------------------------------------------------------------------- |
| 0      |      4 | `type`                       | u32 little-endian; `1` = lookup table                                                  |
| 4      |      8 | `deactivationSlot`           | u64 little-endian; `18446744073709551615` (all bytes `0xFF`) while the table is active |
| 12     |      8 | `lastExtendedSlot`           | u64 little-endian                                                                      |
| 20     |      1 | `lastExtendedSlotStartIndex` | u8                                                                                     |
| 21     |      1 | authority present            | `1` = has an authority, `0` = frozen                                                   |
| 22     |     32 | `authority`                  | public key                                                                             |
| 54     |      2 | padding                      |                                                                                        |
| 56     | 32 × N | `addresses`                  | N stored addresses, at most 256                                                        |

Account size is `56 + 32 × N` bytes. A table with 4 addresses is 184 bytes.

## Filters

| Goal                                      | Filter                                                                      |
| :---------------------------------------- | :-------------------------------------------------------------------------- |
| Tables controlled by an authority         | `memcmp` at offset 22, `bytes` = the authority public key (base58)          |
| Tables with an authority set (not frozen) | `memcmp` at offset 21, `bytes: "2"` (base58 of the single byte `0x01`)      |
| Active tables only                        | `memcmp` at offset 4, `bytes: "jpXCZedGfVQ"` (base58 of eight `0xFF` bytes) |
| Tables holding exactly N addresses        | `dataSize: 56 + 32 × N`                                                     |
| Tables whose address at index i is X      | `memcmp` at offset `56 + 32 × i`, `bytes` = X                               |

`memcmp` compares at one fixed offset, so a table that holds an address at an unknown position matches only a filter for that position. There is no "contains" filter.

Filters combine with AND. `memcmp` bytes may be base58 (default) or base64 via `memcmp.encoding`.

## Examples

`getProgramAccounts` on the tables controlled by one authority, with no account data returned:

```bash
curl "https://rpc-newyork.constant-k.com/?api-key=YOUR_API_KEY" \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "AddressLookupTab1e1111111111111111111111111",
      {
        "filters": [
          { "memcmp": { "offset": 22, "bytes": "DStdNqtygUnfNwkHNrEhsPWwN7hqXrYrMJ8JMkLvidjT" } }
        ],
        "dataSlice": { "offset": 0, "length": 0 }
      }
    ]
  }'
```

`dataSlice` with `length: 0` returns no data, so the answer carries only the table addresses and account metadata. The response is trimmed to one account:

```json
[
  {
    "account": {
      "data": ["", "base64"],
      "executable": false,
      "lamports": 2171520,
      "owner": "AddressLookupTab1e1111111111111111111111111",
      "rentEpoch": 18446744073709551615,
      "space": 184
    },
    "pubkey": "1AjuJovi8byYxtUPyFna86jaRipDgVeX4TCXbkWNZxg"
  }
]
```

`getProgramAccountsV2` returns one page of tables with the layout decoded:

```bash
curl "https://rpc-newyork.constant-k.com/?api-key=YOUR_API_KEY" \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccountsV2",
    "params": [
      "AddressLookupTab1e1111111111111111111111111",
      {
        "encoding": "jsonParsed",
        "filters": [{ "dataSize": 184 }],
        "limit": 1,
        "withContext": true
      }
    ]
  }'
```

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "context": { "slot": 457692691 },
    "value": {
      "accounts": [
        {
          "account": {
            "data": {
              "parsed": {
                "info": {
                  "addresses": [
                    "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                    "SysvarRent111111111111111111111111111111111",
                    "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1",
                    "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"
                  ],
                  "authority": "DStdNqtygUnfNwkHNrEhsPWwN7hqXrYrMJ8JMkLvidjT",
                  "deactivationSlot": "18446744073709551615",
                  "lastExtendedSlot": "306248204",
                  "lastExtendedSlotStartIndex": 0
                },
                "type": "lookupTable"
              },
              "program": "address-lookup-table",
              "space": 184
            },
            "executable": false,
            "lamports": 2171520,
            "owner": "AddressLookupTab1e1111111111111111111111111",
            "rentEpoch": 18446744073709551615,
            "space": 184
          },
          "pubkey": "1AjuJovi8byYxtUPyFna86jaRipDgVeX4TCXbkWNZxg"
        }
      ],
      "paginationKey": "1AjuJovi8byYxtUPyFna86jaRipDgVeX4TCXbkWNZxg"
    }
  }
}
```

`paginationKey` is the cursor for the next page. It is passed back as `paginationKey` on the next call, and is `null` on the last page.

## Parameters

| Parameter          | Values                                                    | Notes                                                                                                                                                              |
| :----------------- | :-------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `encoding`         | `base64` (default), `base64+zstd`, `base58`, `jsonParsed` | `base58` covers data up to 128 bytes after `dataSlice`; larger data returns `-32602` with `base58 encoding is only supported for data under 129 bytes; use base64` |
| `dataSlice`        | `{ offset, length }`                                      | `length: 0` returns no data, only addresses and account metadata                                                                                                   |
| `filters`          | `dataSize`, `memcmp`                                      | Optional; every filter must match                                                                                                                                  |
| `withContext`      | `true`, `false`                                           | Adds `context.slot`                                                                                                                                                |
| `minContextSlot`   | slot number                                               | If the index has not reached it, `-32016` with `Minimum context slot has not been reached`                                                                         |
| `commitment`       | any level                                                 | Accepted, no effect. See the note below                                                                                                                            |
| `limit`            | 1 to 10,000, default 1,000                                | `getProgramAccountsV2` only. Outside the range: `-32602` with `limit must be between 1 and 10000`                                                                  |
| `paginationKey`    | account address                                           | `getProgramAccountsV2` only; the cursor from the previous page                                                                                                     |
| `changedSinceSlot` | slot number                                               | `getProgramAccountsV2` only; returns only tables changed since that slot, for incremental sync                                                                     |

> **commitment is accepted and ignored:** Answers are at confirmed commitment. A finalized request gets confirmed data.

## Result size and freshness

`getProgramAccounts` answers up to 1 GB of matched account data. Above that it returns `-32012` with `scan aborted: The accumulated scan results exceeded the limit`. `getProgramAccountsV2` pages through any size, so walking every table uses it with `limit` up to 10,000 and `paginationKey`. Results are narrowed with `filters` and `dataSlice`.

If the index falls behind the chain, a `getProgramAccountsV2` call with `changedSinceSlot` returns `-32005` with `data.numSlotsBehind` until the index catches up.

## Errors

| HTTP  |   Code   | Message                                                                                                       | Cause                                                                                  |
| :---- | :------: | :------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------- |
| `403` | `-32600` | `getProgramAccounts requires higher tier`                                                                     | Free or Operator plan. The `getProgramAccountsV2` message names `getProgramAccountsV2` |
| `429` | `-32005` | `Rate limit exceeded (gpa: 25/sec)`, `Rate limit exceeded (gpa: 75/sec)`                                      | Over the plan's calls per second                                                       |
| `200` | `-32602` | `base58 encoding is only supported for data under 129 bytes; use base64`, `limit must be between 1 and 10000` | An invalid parameter                                                                   |
| `200` | `-32012` | `scan aborted: The accumulated scan results exceeded the limit`                                               | Matched account data over 1 GB. `getProgramAccounts` only                              |
| `200` | `-32016` | `Minimum context slot has not been reached`                                                                   | `minContextSlot` is above the slot the index has reached                               |
| `200` | `-32005` | Error `data` carries `numSlotsBehind`                                                                         | The index is behind the chain. Only with `changedSinceSlot`                            |

See [Errors](https://docs.constant-k.com/rpc/errors/) for every code and its fix.
