关键词:Solana、getProgramAccounts、程序账户、Token 账户、RPC 调用、dataSize、memcmp、dataSlice
为什么你需要 getProgramAccounts?
作为一名开发者或深度 DeFi 用户,你的日常场景往往绕不开「一次性拉取一批关联账户」:
- 查找某个钱包地址拥有的所有代币账户
- 统计某个 SPL Token 的持币人数
- 爬取自定义程序下业务数据的地址列表
这些需求 不需要分页 也能快速实现,只要用对 solana-web3.js(或 Rust Geyser)提供的 getProgramAccounts 方法,再辅以 dataSlice、filters 参数即可。
快速了解请求体
基本组成
| 关键字 | 作用 | 必选 / 可选 |
|---|---|---|
| programId | 目标程序的公钥(Base58 字符串) | 必选 |
| configOrCommitment | 对象,包含以下子字段 | 可选 |
commitment: 状态承诺等级encoding: 数据编码格式(base58 / base64 / jsonParsed)dataSlice: 只截取数据的某一段,降低带宽占用filters: 同时叠加多个条件,精确缩小结果范围withContext: 是否返回统一包装结构
响应结构示例
[
{
"pubkey": "G...xx",
"account": {
"lamports": 2039280,
"owner": "Tokenkeg...Q5DA",
"data": "...",
"executable": false,
"rentEpoch": 315
}
}
]实战:三种高频需求
1 查询某个钱包的全部代币账户(带余额)
核心关键词:代币账户、钱包、memcmp、Token Program
场景:你想把一个钱包的所有 SPL Token 收入「资产仪表盘」。
步骤:
- 设
dataSize=165:因为 SPL Token 账户固定 165 字节。 offset = 32处比对owner = 钱包地址,用memcmp即可。
JavaScript 精简版:
const accounts = await connection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID,
{
filters: [
{ dataSize: 165 },
{
memcmp: {
offset: 32, // owner 字段偏移
bytes: MY_WALLET_ADDR
}
},
]
}
);输出能拿到的关键信息:
mint(币种)tokenAmount.uiAmount(余额)pubkey(代币账户地址,直接在本地缓存,避免二次查询)
2 统计某代币的持币地址数量
核心关键词:持有者、枚举账户、dataSlice、节省带宽
你不想拿完整数据,只想知道大致有多少个地址持有该代币,怎么办?
dataSlice: { offset: 0, length: 0 }:返回空数据段,只保留pubkey与lamports。在
filters中加入:dataSize: 165memcmp(offset: 0, bytes: TOKEN_MINT_ADDRESS)
这样就能快速返回所有持有者的 账户地址,而不拖慢网络。
Rust 片段:
let filters = Some(vec![
RpcFilterType::DataSize(165),
RpcFilterType::Memcmp(Memcmp::new(
0,
MemcmpEncodedBytes::Base58(MY_TOKEN_MINT_ADDRESS.to_string()),
)),
]);3 查询自定义程序的账户清单
很多 DApp 会把用户数据塞进 PDA(Program Derived Address),同样的套路:
filters: [
{ dataSize: CUSTOM_ACCOUNT_SIZE },
{ memcmp: { offset: 8, bytes: USER_PUBLICKEY } }
]通过把「程序」换成自己的合约地址,你就能:
- 批量拉取用户挂单、池子份额、投票记录……
- 先缓存地址,再按需走
getMultipleAccounts提速显示。
FAQ:最常见疑问一次说清
Q1 为什么调用超时或返回 413?
A:节点一次性扫描全量会吃内存。务必加入 filters 并使用 dataSlice,或将查询拆成多次小批次。
Q2 能把 Base64 数据直接当成 JSON 对象用吗?
A:用 jsonParsed 编码可直接得到解析后的对象,不再手 parse;对前端更友好,但对交易签名环节建议回退为 base64 或 base58 以保持一致性。
Q3 未来会支持分页吗?
A:官方路线图提到会以「多键游标」方式迭代式拉取,目前尚未发布。当前最佳实践仍是尽量缩减查询范围。
Q4 Rust 与 JS 的编译差异需要留意什么?
A:Rust 的 RpcFilterType 与 JS 的 filters 对象的顺序一致即可,但 Base64 拼接需手动确认 UTF-8 编码一致性。
Q5 如何把结果写入数据库缓存?
A:把 pubkey 设为表主键,并以账户的 lamports、owner、data.length 作为一致性校验字段;每 5~10 分钟 diff 一次即可做增量同步。
数据一致性与最佳实践
- 合理分批:一次返回 2 万以上记录易超时,建议再嵌一层
timelock或timestamp二级过滤。 - local cache:把
pubkey加 LRU map,前端快速回显,后台异步更新余额。 - 多节点挂载:对高频业务(Bot、行情服务器)可做轮流调用,避免单节点限流。
- 埋点监控:统计每次 RPC 延迟、返回包大小,一旦异常先回落
getAccountInfo单点查询。
小结
getProgramAccounts 是 Solana 开发者的大杀器,核心思路只有一句:「尽可能在前置阶段就把数据圈定」,利用 dataSize、memcmp、dataSlice 三板斧,你就能:
- 毫秒级列出某钱包的全部资产;
- 分钟级完成全网的 Token 持币地址扫描;
- 无需分页也能把大型程序账户安全拉回本地。
为你的链上业务补上最后一环,现在就动手!