Written by
0xIchigo
Published on
December 6, 2024
Copy link

Introducing: Faster getProgramAccounts (gPA) Calls

getProgramAccounts (gPA) calls are notoriously plagued with issues. This RPC method is an expensive and inefficient operation that queries a node to fetch all the accounts owned by a given public key. These calls are often slow, and heavily rate-limited. Sometimes, these calls are outright banned (e.g., making a gPA call on Serum’s program), if the results aren’t already cached. These issues have forced developers to seek out painstaking alternatives that aren’t as efficient. 

This changes today.

At Helius, we’re introducing faster getProgramAccounts calls for all Solana developers. 

Here’s what’s new:

  • We’ve significantly improved our account indexing
  • You can expect gPA calls to be 2-10x faster than before, especially when using filters for larger programs
  • We automatically index a program after a single call from any developer, resulting in better performance for everyone

Getting Started

To get started, sign up on the Helius Developer Dashboard and obtain an API key under the “API Keys” section. 

Basic getProgramAccounts Example

Let’s query all the accounts owned by the Ore V2 program in JavaScript:

const url = `https://mainnet.helius-rpc.com/?api-key=<API_KEY>`;

const getProgramAccounts = async () => {
  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: "test",
        method: "getProgramAccounts",
        params: [
          "oreV2ZymfyeXgNgBdqMkumTqqAprVqgBWQfoYkrtKWQ",
          {
            "encoding": "base64",
          },
          
        ],
        
      })
    });

    const data = await response.json();

    console.log(`All Accounts Owned By The Ore v2 Program: ${JSON.stringify(data, null, 2)}`);
  } catch (error) {
    console.error(error);
  }
};

getProgramAccounts();

Let’s break down how the code works:

  1. URL Configuration: We create a URL that points to the Helius RPC endpoint and supply our API key
  2. RPC Request Structure
    • method: “getProgramAccounts” specifies we want to query program-owned accounts
    • params is an array containing two elements: the program ID we want to query (in this case, Ore’s V2 Program) and a configuration object for the query
  3. Encoding: We request the account data in base64 encoding
  4. Error Handling: We handle any errors thrown with a try/catch block

Running this code will log all the accounts owned by Ore’s V2 program to the console. 

However, this is a basic example—you’ll typically want to use filters to narrow down the results and improve performance.

getProgramAccounts Example with Filters

Let’s look at a more practical example where we query all the token accounts owned by a specific address using filters:

const url = `https://mainnet.helius-rpc.com/?api-key=<API_KEY>`;

const getTokenAccounts = async () => {
  try {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: "token-accounts",
        method: "getProgramAccounts",
        params: [
          "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",  // Token Program address
          {
            "encoding": "jsonParsed",  // Get parsed token data
            "filters": [
              {
                "dataSize": 165,  // Size of token account data
              },
              {
                "memcmp": {
                  "offset": 32,  // Location of owner address in the token account
                  "bytes": "YOUR_WALLET_ADDRESS" 
                }
              }
            ]
          }
        ]
      })
    });
    const data = await response.json();
    
    data.result.forEach((account, i) => {
      const parsed = account.account.data.parsed.info;

      console.log(`-- Token Account ${i + 1}: ${account.pubkey} --`);
      console.log(`Mint: ${parsed.mint}`);
      console.log(`Amount: ${parsed.tokenAmount.uiAmount}`);
    });
  } catch (error) {
    console.error("Error fetching token accounts:", error);
  }
};

getTokenAccounts();

This example builds upon the basic example to demonstrate two important filtering techniques: using a dataSize and memcmp filter.

dataSize

The dataSize filter checks the exact data size of a given account. In our case, we’re interested in token accounts, which are 165 bytes. This will immediately filter out any other accounts owned by the provided wallet that aren’t a token account.

memcmp Filter (Memory Comparison)

The memcmp filter, or memory comparison filter, allows us to compare data stored at a specific location in memory. We use an offset to specify the position to begin comparing data. 

In our example, we use an offset of 32 to skip past the mint address, which is stored in the first 32 bytes of memory, as we’re only interested in the owner address. This filter will only return accounts owned by the specified wallet address.

Running this code will log a list of all token accounts and their balances for the provided wallet address to the console. With Helius’s improved indexing, these filtered queries are significantly faster than those of other traditional RPC providers.

Additional Help

Ready to experience fewer headaches and faster getProgramAccounts calls? 

Sign up on the Helius Developer Dashboard and start building with better performance, today. Need help? Visit the Helius Discord for any questions or support!

If you’ve read this far, thank you, anon! Be sure to enter your email address below so you’ll never miss an update about what’s new on Solana. On a learning binge? Explore the latest articles on our blog and accelerate your Solana journey.