getProgramAccounts RPC 方法是查询 Solana 区块链的强大工具。它允许您检索由特定链上程序拥有的所有账户。这对于广泛的应用程序至关重要,从查找与特定代币铸造相关的用户的所有代币账户,到发现去中心化应用程序的所有用户特定数据账户。 由于程序可能拥有的账户数量可能很大,getProgramAccounts 提供了强大的过滤功能,帮助您缩小搜索范围并高效地检索所需数据。 对于需要查询非常大规模程序账户的应用程序,考虑使用 getProgramAccountsV2,它提供基于游标的分页支持,配置的页面大小每次请求最多可达 10,000 个账户。

常见用例

  • 查找铸币的所有代币账户: 发现特定 SPL 代币的所有持有者。
  • 检索用户特定数据: 获取由程序为特定用户创建的所有账户(例如,用户在 DeFi 协议中的头寸,他们在 Play-to-Earn 游戏中的游戏状态)。
  • 列出自定义账户类型的所有实例: 如果您的程序定义了特定的账户结构,getProgramAccounts 可以找到该结构的所有实例。
  • 监控程序状态: 观察与程序相关的所有账户以跟踪其整体状态或活动。
  • 构建浏览器和分析工具: 聚合有关程序及其关联账户的数据。

请求参数

  1. programId (string,必需):
    • 您要获取其账户的程序的 base-58 编码公钥。
    • 示例:"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"(用于 SPL 代币程序)。
  2. options (object, 可选): 一个配置对象,包含以下字段:
    • commitment (string): 指定承诺级别(例如,"finalized", "confirmed")。
    • encoding (string): 每个返回账户中data字段的编码。默认为"base64"
      • "base58": 二进制数据的较慢替代方案。
      • "base64": 二进制数据的标准base64编码。
      • "base64+zstd": base64编码的zstd压缩二进制数据。
      • "jsonParsed": 如果RPC节点有程序账户类型的解析器(例如,SPL Token, Stake),则data字段将是一个结构化的JSON对象。强烈推荐用于可读性和易用性。
    • filters (array): 一个应用于账户的过滤对象数组。这对于性能和相关性至关重要。您可以使用最多4个过滤器。常见的过滤器包括:
      • dataSize (object):
        • dataSize (u64): 按数据长度(字节)过滤账户。例如:{ "dataSize": 165 }(用于SPL Token账户)。
      • memcmp (object): 内存比较。将账户数据的一个切片与提供的字节进行比较。
        • offset (usize): 开始比较的账户数据字节偏移量。
        • bytes (string): 要匹配的字节的base-58编码字符串。字节串必须小于129字节。
        • 示例:要查找特定铸币的代币账户,您可以使用memcmp,其中offset: 0(铸币地址存储在代币账户中)和bytes设置为铸币的公钥。
    • dataSlice (object): 仅返回每个账户数据的特定切片。对于只需要部分数据的大账户非常有用。
      • offset (usize): 开始切片的字节偏移量。
      • length (usize): 要返回的字节数。
      • 注意:dataSlice主要用于二进制编码,而不是jsonParsed
    • withContext (boolean): 如果true,响应将是一个RpcResponse对象,包含一个context(带有slot)和value(账户数组)。如果false或省略,通常只返回账户数组。行为可能因RPC提供商而略有不同。
    • minContextSlot (u64): 请求可以评估的最小槽位。

响应结构

响应是一个对象数组,其中每个对象代表一个找到的账户,并包括:
  • pubkey (string):账户的 base-58 编码公钥。
  • account (object):
    • lamports (u64):账户的 lamports 余额。
    • owner (string):拥有此账户的程序的 base-58 编码公钥(这将是您查询的 programId)。
    • data (string, array, 或 object):账户的数据,根据 encoding 参数格式化。
      • 对于 jsonParsed:表示反序列化账户状态的 JSON 对象。
      • 对于 base64:一个数组 ["encoded_string", "base64"]
    • executable (boolean):账户是否可执行(即自身是一个程序)。
    • rentEpoch (u64):此账户下次需要支付租金的纪元。
    • space (u64, 可选):账户的数据长度,以字节为单位。有时称为 data.length,如果数据是一个缓冲区,或是解析结构的一部分。
如果使用 withContext: true,此数组将嵌套在 value 字段下的一个 RpcResponse 对象中。

示例

1. 查找特定铸币(USDC)的所有代币账户

此示例查找持有 USDC 的所有 SPL 代币账户。它使用 dataSize 过滤代币账户(165 字节),并使用 memcmp 匹配偏移量 0 处的 USDC 铸币地址。
# Replace <api-key> with your Helius API key
# USDC Mint: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
# Token Program ID: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
curl https://mainnet.helius-rpc.com/?api-key=<api-key> -X POST -H "Content-Type: application/json" -d \
  '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        "encoding": "jsonParsed",
        "filters": [
          { "dataSize": 165 },
          {
            "memcmp": {
              "offset": 0, 
              "bytes": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
            }
          }
        ]
      }
    ]
  }'

2. 查找由特定钱包拥有的所有代币账户

此示例查找由特定钱包地址拥有的所有 SPL 代币账户。它使用 dataSize(165 字节)和 memcmp 在偏移量 32(代币账户中存储所有者公钥的位置)。
# Replace <api-key> with your Helius API key
# Example Wallet Address: Helioo21241PANoNdeG55722hgUnp2VawDgsz2g
# Token Program ID: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
curl https://mainnet.helius-rpc.com/?api-key=<api-key> -X POST -H "Content-Type: application/json" -d \
  '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        "encoding": "jsonParsed",
        "filters": [
          { "dataSize": 165 },
          {
            "memcmp": {
              "offset": 32, 
              "bytes": "Helioo21241PANoNdeG55722hgUnp2VawDgsz2g"
            }
          }
        ]
      }
    ]
  }'

高级过滤

使用过滤器优化查询以减少响应大小并提高性能:
// Example filtering by memcmp (memory comparison)
const response = await fetch(
  "https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "1",
      method: "getProgramAccounts",
      params: [
        "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", // Solana Token Program
        {
          encoding: "jsonParsed",
          filters: [
            {
              dataSize: 165, // Size of token account data
            },
            {
              memcmp: {
                offset: 32, // Location of owner address in the token account
                bytes: "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri",
              },
            },
          ],
        },
      ],
    }),
  }
);
const data = await response.json();
console.log("Filtered program accounts data:", data);

API Reference

getProgramAccounts

过滤器类型

  • memcmp: 过滤在给定偏移量处匹配特定模式的账户
  • dataSize: 按账户的确切数据大小过滤
  • 多个过滤器:所有条件必须满足(逻辑与)

开发者提示

  • 性能: getProgramAccounts 在 RPC 节点上可能资源密集,尤其是在没有过滤器或程序有许多账户时。始终使用过滤器(dataSize, memcmp)和 dataSlice 尽可能减少查询范围和响应大小。
  • 大结果集: 对于返回许多结果的查询,响应可能会被截断或超时。使用过滤器减少范围,或考虑使用 getProgramAccountsV2 以支持分页。
  • 速率限制: 注意 RPC 提供商的速率限制,因为频繁或大量的 getProgramAccounts 调用可能会触及这些限制。
  • 数据布局知识: 有效使用 memcmp 需要了解您查询的账户数据的字节布局。
  • jsonParsed 可用性: jsonParsed 编码取决于 RPC 节点是否有特定程序账户类型的解析器。对于常见程序如 SPL Token 广泛支持。
getProgramAccounts 是开发人员需要查询和与程序拥有的账户集交互的不可或缺的方法。掌握其过滤选项是构建高效且稳健的 Solana 应用程序的关键。

大数据集的分页

对于处理拥有大量账户(10,000+)的程序的应用程序,使用 getProgramAccountsV2 提供:
  • 基于游标的分页:设置limit(1-10,000)并使用paginationKey来浏览结果
  • 增量更新:使用changedSinceSlot仅获取自特定槽位以来修改的账户
  • 更好的性能:防止超时并减少内存使用
  • 分页行为:分页结束仅在没有返回账户时指示。由于过滤,返回的账户可能少于限制 - 继续分页直到paginationKey为null
// Example: Paginated query for all token accounts
const response = await fetch("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: "1",
    method: "getProgramAccountsV2",
    params: [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        encoding: "base64",
        filters: [{ dataSize: 165 }],
        limit: 5000
      }
    ]
  })
});

const data = await response.json();
console.log(`Found ${data.result.accounts.length} accounts`);
if (data.result.paginationKey) {
  console.log("More results available, use paginationKey for next page");
  // Continue pagination even if fewer than limit accounts were returned
} else {
  console.log("End of pagination - no more accounts available");
}