# Accelerated getProgramAccounts

`getProgramAccounts` is one of the most expensive calls on Solana. On a standard RPC node, it scans raw *account* states on every request - slow by design, and worse under load. The root cause is structural: standard nodes have no index over account data, so every call triggers the same full scan regardless of how specific your filter is.

This is exactly what Shyft solves - Introducing **Accelerated getProgramAccounts**.

Shyft's Accelerated `getProgramAccounts` (or Accelerated gPA) <mark style="color:yellow;">resolves</mark> the most <mark style="color:yellow;">common</mark> queries in <mark style="color:yellow;">under 10ms</mark>, no code changes required.

{% hint style="success" %}
Accelerated `getProgramAccounts` is available on all paid plans. Already live on Shyft RPC across all regions. No API changes - same method, same parameters, dramatically faster responses.
{% endhint %}

### Why the standard `getProgramAccounts` is slow?

A vanilla Solana validator has no index over account data. When you call `getProgramAccounts` with a `memcmp` filter, the node walks every account owned by that program and compares bytes at the given offset - one by one, on-the-fly, every time. For large programs like Raydium or the Token Program, which own millions of accounts, this can take anywhere from hundreds of milliseconds to tens of seconds.

<figure><img src="/files/haVha0w71jgFoOJW5VUj" alt=""><figcaption><p>What happens when we call getProgramAccounts on Solana</p></figcaption></figure>

### How it works

Standard Solana RPC nodes handle everything - *block replay, vote processing, account state, and RPC* queries - in one tightly coupled stack. When a `getProgramAccounts` call comes in, it competes for resources with everything else the validator is doing, and scans raw account state on every request.

Shyft's approach is different. The accounts engine runs on entirely separate hardware, <mark style="color:yellow;">decoupled from the RPC layer</mark>. It has one job: maintain a fast, structured view of account data for the programs and offsets that matter most, and serve read requests against it.

#### The single-process architecture

The entire engine runs as a single unified process - no external database connections, no network hops between components. Three responsibilities, one process:

* **gRPC ingestion:** Streams account updates directly from the validator layer as they happen on-chain.
* **RocksDB indexing:** Writes account state into an embedded key-value store, keyed by program + offset.
* **Stateless JSON-RPC:** Serves `getProgramAccounts` reads directly from RocksDB - no external calls.

<figure><img src="/files/4TRbqgwdgEihG98vDXoe" alt=""><figcaption><p>The Shyft Accounts Engine: How It Works</p></figcaption></figure>

#### How acceleration is triggered

When a `getProgramAccounts` request arrives at the JSON-RPC layer, the engine checks two things: is this program in the accelerated set, and does the `memcmp` offset match a keyed index for that program? If both match, the result is served directly from RocksDB. If either condition is not met, the request falls through to the standard RPC path transparently - same response, just without the speed benefit.

{% hint style="info" %}
**No code changes required.** Acceleration is applied at the RPC layer automatically. Your existing `getProgramAccounts` calls work as-is - the engine intercepts matching requests before they ever touch raw ledger state.
{% endhint %}

> **On-demand Acceleration: New program + offset combinations can be added in real time**
>
> The engine currently covers a pre-defined set of programs and offsets selected based on query patterns across the Shyft network. But new indexes are not a deployment - they are created on demand. If you regularly query a program or offset not yet in the accelerated set, reach out to the Shyft team. The index is created without downtime or service interruption, and acceleration applies from that point forward.

### Accelerated programs & offsets

The following program + offset combinations are accelerated. Calls that match are served from the Shyft accounts engine.

<table><thead><tr><th width="199">Protocol</th><th width="392">Program address</th><th>Accelerated offsets</th></tr></thead><tbody><tr><td>Pump.fun AMM</td><td>pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA</td><td>0,43,75</td></tr><tr><td>Pump.fun</td><td>6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P</td><td>0</td></tr><tr><td>Raydium CLMM</td><td>CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK</td><td>0, 73, 105</td></tr><tr><td>Raydium AMM v4</td><td>675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8</td><td>400, 432</td></tr><tr><td>Raydium CPMM</td><td>CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C</td><td>168, 200</td></tr><tr><td>Meteora DLMM</td><td>LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo</td><td>0, 88, 120</td></tr><tr><td>Meteora DAMM v2</td><td>cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG</td><td>168, 200</td></tr><tr><td>Meteora DAMM v1</td><td>Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB</td><td>40, 72</td></tr><tr><td>Orca Whirlpool</td><td>whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc</td><td>0, 101, 181</td></tr><tr><td>Jupiter Lend/Borrow</td><td>jupr81YtYssSyPt8jbnGuiWon5f6x9TcDEFxYe3Bdzi</td><td>0</td></tr><tr><td>Drift</td><td>dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN</td><td>0, 136</td></tr><tr><td>PancakeSwap CLMM</td><td>HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq</td><td>0, 73, 105</td></tr><tr><td>Address Lookup Table</td><td>AddressLookupTab1e1111111111111111111111111111</td><td>22</td></tr><tr><td>Solana Stake Program</td><td>Stake11111111111111111111111111111111111111</td><td>12</td></tr></tbody></table>

{% hint style="info" %}
**Need a program added?** New indexes are created on demand without downtime. Reach out to the Shyft team with the program address and the offsets you query regularly.
{% endhint %}

### How the Engine Handles Multiple Filters

Most real-world `getProgramAccounts` calls don't use a single filter. A trading bot querying Raydium CLMM pools might filter by `token_mint_a` at offset 73 *and* by `token_mint_b` at offset 107 simultaneously - narrowing the result to pools containing a specific trading pair. Here's how the engine handles it:&#x20;

**When one of your filters hits an accelerated offset:**

The engine scans your filter set, identifies the accelerated offset, and resolves that filter first - regardless of where you placed it in the array. The accelerated lookup runs against the purpose-built index and returns a narrow result set immediately. Every remaining filter is then evaluated in-memory against that already-narrow set. <mark style="color:yellow;">**Filter order**</mark>**&#x20;in your request&#x20;**<mark style="color:yellow;">**does not matter**</mark>**&#x20;- the engine&#x20;**<mark style="color:yellow;">**finds the fast path automatically**</mark>**.**

```json
{
  // example when accelerated offset is provided later
  "filters": [
    { "memcmp": { "offset": 107, "bytes": "USDC_MINT_PUBKEY" } },  
    { "memcmp": { "offset": 73,  "bytes": "WSOL_MINT_PUBKEY" } } 
    // the accelerated offset is picked first automatically  
  ]
}
```

Even though the accelerated offset `73` is second in the array, the engine resolves it first. The result is every CLMM pool where `token_mint_a` is WSOL - filtered further in-memory to those where `token_mint_b` is USDC. Two filters, one fast path.

**When none of your filters hit an accelerated offset:**

The engine falls through to the standard scan path — filters evaluated in order, full\
account set, no short-circuit. The request completes correctly, just without the speed\
benefit.&#x20;

{% hint style="info" %}
**For programs in the accelerated set,&#x20;**<mark style="color:yellow;">**including at least one filter**</mark>**&#x20;at an accelerated offset is all it takes — the engine handles the rest.**
{% endhint %}

### Quick Start

A working example for `getProgramAccounts` Pump.fun AMM at offset 43 - in cURL, JavaScript & Rust - the fastest way to see the latency difference yourself.

{% tabs %}
{% tab title="cURL" %}

```bash
curl --location 'https://rpc.shyft.to/?api_key=YOUR-API-KEY' \
--header 'Content-Type: application/json' \
--data '{
    "id": 1,
    "jsonrpc": "2.0",
    "method": "getProgramAccounts",
    "params": [
        "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
        {
            "commitment": "confirmed",
            "encoding": "base64",
            "filters": [
                {
                    "memcmp": {
                        "bytes": "T4T8I/7SrQyFRxwvhoh/IzjSO8qinIinMl3nI/a4le8=",
                        "encoding": "base64",
                        "offset": 43
                    }

                }
            ]
        }
    ]
}'
```

{% endtab %}

{% tab title="JavaScript" %}
{% code overflow="wrap" %}

```javascript
const API_KEY = "YOUR_API_KEY";
const RPC_URL = `https://rpc.shyft.to/?api_key=${API_KEY}`;

const payload = {
  id: 1,
  jsonrpc: "2.0",
  method: "getProgramAccounts",
  params: [
    "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
    {
      commitment: "confirmed",
      encoding: "base64",
      filters: [
        {
          memcmp: {
            offset: 43, //replace with offset needed
            bytes: "T4T8I/7SrQyFRxwvhoh/IzjSO8qinIinMl3nI/a4le8=", //replace with required bytes
            encoding: "base64",
          },
        },
      ],
    },
  ],
};

async function main() {
  const start = Date.now();

  const res = await fetch(RPC_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  const data = await res.json();
  const elapsed = Date.now() - start;

  if (data.error) {
    console.error("RPC error:", data.error);
    return;
  }

  console.log(`accounts found : ${data.result.length}`);
  console.log(`latency        : ${elapsed}ms`);
  console.log(`first account  :`, data.result[0]?.pubkey ?? "none");
}

main();
```

{% endcode %}
{% endtab %}

{% tab title="Rust" %}

```rust
// Cargo.toml dependencies:
// reqwest = { version = "0.12", features = ["json"] }
// tokio   = { version = "1",    features = ["full"] }
// serde   = { version = "1",    features = ["derive"] }
// serde_json = "1"

use serde_json::{json, Value};
use std::time::Instant;

const API_KEY: &str = "YOUR_API_KEY";
const RPC_URL: &str = "https://rpc.shyft.to/";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = format!("{}?api_key={}", RPC_URL, API_KEY);

    let payload = json!({
        "id": 1,
        "jsonrpc": "2.0",
        "method": "getProgramAccounts",
        "params": [
            "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
            {
                "commitment": "confirmed",
                "encoding": "base64",
                "filters": [
                    {
                        "memcmp": {
                            "offset": 43, //replace with needed offset
                            "bytes": "T4T8I/7SrQyFRxwvhoh/IzjSO8qinIinMl3nI/a4le8=",
                            "encoding": "base64"
                        }
                    }
                ]
            }
        ]
    });

    let client = reqwest::Client::new();
    let start = Instant::now();

    let res = client
        .post(&url)
        .json(&payload)
        .send()
        .await?
        .jsonValue>()
        .await?;

    let elapsed = start.elapsed();

    if let Some(err) = res.get("error") {
        eprintln!("RPC error: {}", err);
        return Ok(());
    }

    let accounts = res["result"].as_array().map(|a| a.len()).unwrap_or(0);
    let first = &res["result"][0]["pubkey"];

    println!("accounts found : {}", accounts);
    println!("latency        : {}ms", elapsed.as_millis());
    println!("first account  : {}", first);

    Ok(())
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Filter order does not matter. The engine automatically identifies the accelerated offset from whichever filters you pass and resolves it first - returning a narrow result set from the purpose-built index. Any additional filters are then applied against that already-narrow result set in memory.
{% endhint %}

### Latency results

Tests were run from the AMS region, 10 requests at 1 req/s using [Hey](https://github.com/rakyll/hey) with a persistent TCP connection. Four programs were tested, each at one of their accelerated offsets.

| Program              | Offset | Avg    | p50    | p90    |
| -------------------- | ------ | ------ | ------ | ------ |
| Raydium CLMM         | 73     | 8.7ms  | 7.8ms  | 15.2ms |
| Pump.fun AMM         | 43     | 8.3ms  | 7.9ms  | 11.7ms |
| Address Lookup Table | 22     | 10.9ms | 10.7ms | 15.4ms |
| Orca Whirlpool       | 101    | 8.2ms  | 7.8ms  | 12.4ms |

{% hint style="success" %}
**What to expect in production:** With a persistent TCP connection and repeated calls, the majority of accelerated gPA queries resolve between 10-15ms. A cold first request from a fresh TCP handshake adds approximately 5-10ms.
{% endhint %}

{% hint style="info" %}
Tests used [Hey](https://github.com/rakyll/hey), which keeps the TCP connection open after the first request - representative of real application behaviour where connections are reused.
{% endhint %}

### Frequently Asked Questions

<details>

<summary>How do I know if my request was accelerated?</summary>

You don't need to - and that's intentional. If your request matches an accelerated program + offset, it resolves faster. If it doesn't, it falls through to the standard path. The response format is identical in both cases.

</details>

<details>

<summary>What exactly is the "offset" in a memcmp filter, and how was it determined?</summary>

Every account on Solana stores its data as a raw byte buffer. The structure of that buffer - which fields live at which byte positions - is defined by the program that owns the account. This layout is typically described in the program's IDL (Interface Definition Language).

The `offset` in a `memcmp` filter tells the RPC node: "start reading at byte N in the account data buffer, and compare those bytes against my value." So `offset: 73` on Raydium CLMM means "compare starting at byte 73 of each pool account's data." Byte 73 in a CLMM pool account is where the `token_mint_a` field begins - so filtering there lets you find all pools involving a specific token.

You derive the correct offset by reading the program's IDL or inspecting its account struct definitions in the source code. For Anchor-based programs, each field has a known size and they stack sequentially - so you sum the byte sizes of all fields before the one you want. Shyft identified the most commonly queried fields across each protocol and accelerated those specific offsets.

{% hint style="info" %}
**Practical tip:** If you're using a protocol SDK (e.g. `@raydium-io/raydium-sdk` or `@orca-so/whirlpools-sdk`), the SDK usually constructs the correct `memcmp` filter for you - you don't need to calculate the offset manually.
{% endhint %}

</details>

<details>

<summary>What do you pass as the "bytes" value in the filter?</summary>

The `bytes` value is whatever you're filtering *for* at that offset - encoded as base58 (default) or base64 depending on the `encoding` you specify. It is not a fixed value; it's the specific thing you're looking up.

In most DeFi use cases, the field at an accelerated offset is a public key - a token mint address, a pool authority, or a user wallet. So `bytes` is the base58 or base64 encoded public key you're searching for.

```json
{
  "filters": [
    {
      "memcmp": {
        "offset": 73,
        "bytes": "So11111111111111111111111111111111111111112",
        "encoding": "base58"
      }
    }
  ]
}
```

{% hint style="warning" %}
**Empty results?** The most common cause is passing a base64-encoded value when encoding is set to base58, or vice versa. Double-check the `encoding` field matches how you've encoded the bytes.
{% endhint %}

</details>

<details>

<summary>What happens if I pass multiple memcmp filters at different offsets?</summary>

Filter order does not matter. The engine automatically identifies the accelerated offset from whichever filters you pass and resolves it first - returning a narrow result set from the purpose-built index. Any additional filters are then applied against that already-narrow result set in memory.

```json
{
  "filters": [
    { "memcmp": { "offset": 73,  "bytes": "TOKEN_MINT_PUBKEY" } },
    { "memcmp": { "offset": 200, "bytes": "SOME_OTHER_VALUE"  } } 
    //order doesn't matter
  ]
}
```

</details>

<details>

<summary>Is this available on all Shyft RPC plans?</summary>

Accelerated gPA is included on all paid Shyft RPC plans with no additional configuration. It is not available in the free plan.

</details>

<details>

<summary>Can I request acceleration for a program not on the list?</summary>

Yes. New indexes are not a deployment - they are created in real time on demand without downtime or service interruption. Reach out to the Shyft team with the program address and the offsets you query regularly.

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.shyft.to/solana/accelerated-getprogramaccounts.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
