Get Program Accounts
プログラムに所有されるアカウントすべてを返すRPCメソッド。ページネーションはサポートしていません。 レスポンス向上をしつつ意図した結果のみを返すためには、 getProgramAccounts
にはdataSlice
か filters
、あるいはその両方をパラメータに含める必要があります。
概要
Parameters
programId
:string
- base58 でエンコードされた文字列として提供される、クエリするプログラムの公開鍵- (optional)
configOrCommitment
:object
- 次のオプションフィールドを含む設定パラメータ:- (optional)
commitment
:string
- State commitment - (optional)
encoding
:string
- アカウント データのエンコード:base58
、base64
、jsonParsed
のいずれか。 ※ web3js ユーザーは代わりにgetParsedProgramAccountsを使用する必要があります。 - (optional)
dataSlice
:object
- 下記に基づき、返却されるアカウントデータを制限します。:offset
:number
- アカウントデータの返却開始位置バイト数length
:number
- アカウントデータの返却バイト数
- (optional)
filters
:array
- 次のフィルターオブジェクトを使用して結果をフィルタします。:memcmp
:object
- アカウントデータと照合する連続バイト:offset
:number
- アカウントデータの比較開始位置バイト数bytes
:string
- 照合に使用する129バイト制限のbase58エンコード文字列
dataSize
:number
- アカウントデータの長さと比較する指定数
- (optional)
withContext
:boolean
- 結果をRpcResponse JSON objectにラップするかどうか。
- (optional)
Response
デフォルトでは、 getProgramAccounts
は次の構造を持つ JSON オブジェクトの配列を返します。:
pubkey
:string
- base58エンコード文字列のアカウント公開鍵account
:object
- 次のサブフィールドを持つ JSON オブジェクト:lamports
:number
, アカウントに割り当てられたlamports の数owner
:string
, アカウントが割り当てられているプログラムのbase58エンコード文字列のアカウント公開鍵data
:string
|object
- 指定されたエンコーディング パラメータに応じてバイナリ データまたは JSON 形式にエンコードされたアカウントに関連付けられたデータexecutable
:boolean
, アカウントにプログラムが含まれているかどうかrentEpoch
:number
, このアカウントが次にrentを支払うべきepoch
詳細
getProgramAccounts
はプログラムが所有するすべてのアカウントを返す多用途の RPC メソッドです。下記のような検索など、多くのクエリに使用できます:
- 特定のウォレットのすべてのトークン アカウントの取得
- 特定のmintのすべてのトークン アカウント ( つまり、SRM の所有者すべて)
- 特定のプログラムのすべてのカスタム アカウント (つまり、Mango ユーザー全員)
getProgramAccounts
は非常に便利ですが、現在の制約のためによく誤解されます。 サポートされているクエリの多くは、大量のデータ セットをスキャンするためにRPC ノードを必要とします。 これらのスキャンは、メモリとリソースの両方を集中的に使用します。その結果、呼び出しの頻度が高すぎたり取得範囲が大きすぎたりすると、 接続タイムアウトが発生する可能性があります。 さらに、この記事の執筆時点では、getProgramAccounts
エンドポイントはページネーションをサポートしていません。 クエリの結果が大きすぎる場合、レスポンスは破棄されます。
これらの現在の制約を回避するために、dataSlice
、filters
、memcmp
、 dataSize
などの有用なパラメータが提供されています。 これらのパラメーターの組み合わせにより、クエリの範囲を予測可能なサイズに縮小できます。
getProgramAccounts
の一般的な例として、SPL-Token Programとの対話があります。 basic callでトークン プログラムが所有するすべてのアカウントを要求すると、膨大な量のデータが必要になります。 ただし、パラメーターを指定することで、使用するデータのみを効率的に取得できます。
filters
getProgramAccounts
使用する最も一般的なパラメーターは、 filters
配列です。 dataSize
と memcmp
の 2 種類のフィルターを受け入れます。 これらのフィルターのいずれかを使用する前に、要求しているデータがどのように配置され、シリアル化されるかを理解する必要があります。
dataSize
トークン プログラムの場合、トークン アカウントの長さは 165 バイトです。 具体的には、トークンアカウントには8つの異なるフィールドがあり、各フィールドには予測可能なバイト数が必要です。 以下の図を使用して、このデータがどのように配置されているかを視覚化できます。
ウォレットアドレスが所有するすべてのトークンアカウントを検索する場合は、filters
に { dataSize: 165 }
を追加することで、クエリの範囲を正確に165 バイトの長さのアカウントだけに絞り込むことができます。ただし、これだけでは不十分です。 アドレスが所有するアカウントを検索するフィルターも追加する必要があります。これは memcmp
フィルターで実現できます。
memcmp
memcmp
、または "memory comparison"フィルターを使用すると、アカウントに保存されている任意のフィールドのデータを比較できます。 具体的には、特定の位置にある特定のバイト セットに一致するアカウントのみを照会できます。 memcmp
には 2 つの引数が必要です:
offset
: データの比較を開始する位置。この位置はバイト単位で測定され、整数として表されます。bytes
: アカウントのデータと一致させるデータ。これは、base-58エンコードの文字列で、129 バイト未満に制限する必要があります。
memcmp
は bytes
が完全に一致する結果のみを返すことに注意してください。 現在は、指定したbytes
より大きい、または小さい値の比較はサポートされていません。
トークン プログラムの例に沿って、クエリを修正して、ウォレット アドレスが所有するトークン アカウントのみを返すことができます。トークン アカウントを見ると、トークン アカウントに保存されている最初の 2 つのフィールドが両方とも公開鍵であり、各公開鍵の長さが 32 バイトであることがわかります。owner
が 2 番目のフィールドであることを考えると、 memcmp
は 32バイトのoffset
で開始する必要があります。ここから、所有者フィールドがウォレット アドレスと一致するアカウントを探します。
次の例を使用して、このクエリを呼び出すことができます:
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { clusterApiUrl, Connection } from "@solana/web3.js";
(async () => {
const MY_WALLET_ADDRESS = "FriELggez2Dy3phZeHHAdpcoEXkKQVkv6tx3zDtCVP8T";
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const accounts = await connection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
{
filters: [
{
dataSize: 165, // number of bytes
},
{
memcmp: {
offset: 32, // number of bytes
bytes: MY_WALLET_ADDRESS, // base58 encoded string
},
},
],
}
);
console.log(
`Found ${accounts.length} token account(s) for wallet ${MY_WALLET_ADDRESS}: `
);
accounts.forEach((account, i) => {
console.log(
`-- Token Account Address ${i + 1}: ${account.pubkey.toString()} --`
);
console.log(`Mint: ${account.account.data["parsed"]["info"]["mint"]}`);
console.log(
`Amount: ${account.account.data["parsed"]["info"]["tokenAmount"]["uiAmount"]}`
);
});
/*
// Output
Found 2 token account(s) for wallet FriELggez2Dy3phZeHHAdpcoEXkKQVkv6tx3zDtCVP8T:
-- Token Account Address 0: H12yCcKLHFJFfohkeKiN8v3zgaLnUMwRcnJTyB4igAsy --
Mint: CKKDsBT6KiT4GDKs3e39Ue9tDkhuGUKM3cC2a7pmV9YK
Amount: 1
-- Token Account Address 1: Et3bNDxe2wP1yE5ao6mMvUByQUHg8nZTndpJNvfKLdCb --
Mint: BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf
Amount: 3
*/
})();
use solana_client::{
rpc_client::RpcClient,
rpc_filter::{RpcFilterType, Memcmp, MemcmpEncodedBytes, MemcmpEncoding},
rpc_config::{RpcProgramAccountsConfig, RpcAccountInfoConfig},
};
use solana_sdk::{commitment_config::CommitmentConfig, program_pack::Pack};
use spl_token::{state::{Mint, Account}};
use solana_account_decoder::{UiAccountEncoding};
fn main() {
const MY_WALLET_ADDRESS: &str = "FriELggez2Dy3phZeHHAdpcoEXkKQVkv6tx3zDtCVP8T";
let rpc_url = String::from("http://api.devnet.solana.com");
let connection = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
let filters = Some(vec![
RpcFilterType::Memcmp(Memcmp::new(
32, // number of bytes
MemcmpEncodedBytes::Base58(MY_WALLET_ADDRESS.to_string()),
)),
RpcFilterType::DataSize(165),
]);
let accounts = connection.get_program_accounts_with_config(
&spl_token::ID,
RpcProgramAccountsConfig {
filters,
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
commitment: Some(connection.commitment()),
..RpcAccountInfoConfig::default()
},
..RpcProgramAccountsConfig::default()
},
).unwrap();
println!("Found {:?} token account(s) for wallet {MY_WALLET_ADDRESS}: ", accounts.len());
for (i, account) in accounts.iter().enumerate() {
println!("-- Token Account Address {:?}: {:?} --", i, account.0);
let mint_token_account = Account::unpack_from_slice(account.1.data.as_slice()).unwrap();
println!("Mint: {:?}", mint_token_account.mint);
let mint_account_data = connection.get_account_data(&mint_token_account.mint).unwrap();
let mint = Mint::unpack_from_slice(mint_account_data.as_slice()).unwrap();
println!("Amount: {:?}", mint_token_account.amount as f64 /10usize.pow(mint.decimals as u32) as f64);
}
}
/*
// Output
Found 2 token account(s) for wallet FriELggez2Dy3phZeHHAdpcoEXkKQVkv6tx3zDtCVP8T:
-- Token Account Address 0: H12yCcKLHFJFfohkeKiN8v3zgaLnUMwRcnJTyB4igAsy --
Mint: CKKDsBT6KiT4GDKs3e39Ue9tDkhuGUKM3cC2a7pmV9YK
Amount: 1.0
-- Token Account Address 1: Et3bNDxe2wP1yE5ao6mMvUByQUHg8nZTndpJNvfKLdCb --
Mint: BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf
Amount: 3.0
*/
curl http://api.mainnet-beta.solana.com -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": "FriELggez2Dy3phZeHHAdpcoEXkKQVkv6tx3zDtCVP8T"
}
}
]
}
]
}
'
# Output:
# {
# "jsonrpc": "2.0",
# "result": [
# {
# "account": {
# "data": {
# "parsed": {
# "info": {
# "isNative": false,
# "mint": "BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf",
# "owner": "FriELggez2Dy3phZeHHAdpcoEXkKQVkv6tx3zDtCVP8T",
# "state": "initialized",
# "tokenAmount": {
# "amount": "998999999000000000",
# "decimals": 9,
# "uiAmount": 998999999,
# "uiAmountString": "998999999"
# }
# },
# "type": "account"
# },
# "program": "spl-token",
# "space": 165
# },
# "executable": false,
# "lamports": 2039280,
# "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
# "rentEpoch": 313
# },
# "pubkey": "Et3bNDxe2wP1yE5ao6mMvUByQUHg8nZTndpJNvfKLdCb"
# }
# ],
# "id": 1
# }
dataSlice
2 つのフィルター パラメーター以外で、 getProgramAccounts
の 3番目に一般的なパラメーターは dataSlice
です。 filters
パラメーターとは違い、dataSlice
はクエリの結果によって返却されるアカウントの数を制限するわけではありません。 代わりに、dataSlice
それぞれのアカウントのデータ量を制限します。
memcmp
と同様、dataSlice
は下記の2つの引数を受け入れます:
offset
: アカウントデータの返却開始位置(バイト数)length
: 返却を期待するバイト数
dataSlice
は、アカウントデータ自体は重要ではないが大きいデータセットに対してクエリする場合に便利です。 例としては、特定のトークンmintのトークンアカウントの数 (つまり、トークン所有者の数) を見つけたい場合です。
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { clusterApiUrl, Connection } from "@solana/web3.js";
(async () => {
const MY_TOKEN_MINT_ADDRESS = "BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf";
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
const accounts = await connection.getProgramAccounts(
TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
{
dataSlice: {
offset: 0, // number of bytes
length: 0, // number of bytes
},
filters: [
{
dataSize: 165, // number of bytes
},
{
memcmp: {
offset: 0, // number of bytes
bytes: MY_TOKEN_MINT_ADDRESS, // base58 encoded string
},
},
],
}
);
console.log(
`Found ${accounts.length} token account(s) for mint ${MY_TOKEN_MINT_ADDRESS}`
);
console.log(accounts);
/*
// Output (notice the empty <Buffer > at acccount.data)
Found 3 token account(s) for mint BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf
[
{
account: {
data: <Buffer >,
executable: false,
lamports: 2039280,
owner: [PublicKey],
rentEpoch: 228
},
pubkey: PublicKey {
_bn: <BN: a8aca7a3132e74db2ca37bfcd66f4450f4631a5464b62fffbd83c48ef814d8d7>
}
},
{
account: {
data: <Buffer >,
executable: false,
lamports: 2039280,
owner: [PublicKey],
rentEpoch: 228
},
pubkey: PublicKey {
_bn: <BN: ce3b7b906c2ff6c6b62dc4798136ec017611078443918b2fad1cadff3c2e0448>
}
},
{
account: {
data: <Buffer >,
executable: false,
lamports: 2039280,
owner: [PublicKey],
rentEpoch: 228
},
pubkey: PublicKey {
_bn: <BN: d4560e42cb24472b0e1203ff4b0079d6452b19367b701643fa4ac33e0501cb1>
}
}
]
*/
})();
use solana_client::{
rpc_client::RpcClient,
rpc_filter::{RpcFilterType, Memcmp, MemcmpEncodedBytes, MemcmpEncoding},
rpc_config::{RpcProgramAccountsConfig, RpcAccountInfoConfig},
};
use solana_sdk::{commitment_config::CommitmentConfig};
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
pub fn main() {
const MY_TOKEN_MINT_ADDRESS: &str = "BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf";
let rpc_url = String::from("http://api.devnet.solana.com");
let connection = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed());
let filters = Some(vec![
RpcFilterType::Memcmp(Memcmp::new(
0, // number of bytes
MemcmpEncodedBytes::Base58(MY_TOKEN_MINT_ADDRESS.to_string()),
)),
RpcFilterType::DataSize(165), // number of bytes
]);
let accounts = connection.get_program_accounts_with_config(
&spl_token::ID,
RpcProgramAccountsConfig {
filters,
account_config: RpcAccountInfoConfig {
data_slice: Some(UiDataSliceConfig {
offset: 0, // number of bytes
length: 0, // number of bytes
}),
encoding: Some(UiAccountEncoding::Base64),
commitment: Some(connection.commitment()),
..RpcAccountInfoConfig::default()
},
..RpcProgramAccountsConfig::default()
},
).unwrap();
println!("Found {:?} token account(s) for mint {MY_TOKEN_MINT_ADDRESS}: ", accounts.len());
println!("{:#?}", accounts);
}
/*
// Output (notice zero `len` in `data` of `Account`s)
Found 3 token account(s) for mint BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf:
[
(
tofD3NzLfZ5pWG91JcnbfsAbfMcFF2SRRp3ChnjeTcL,
Account {
lamports: 2039280,
data.len: 0,
owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA,
executable: false,
rent_epoch: 319,
},
),
(
CMSC2GeWDsTPjfnhzCZHEqGRjKseBhrWaC2zNcfQQuGS,
Account {
lamports: 2039280,
data.len: 0,
owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA,
executable: false,
rent_epoch: 318,
},
),
(
Et3bNDxe2wP1yE5ao6mMvUByQUHg8nZTndpJNvfKLdCb,
Account {
lamports: 2039280,
data.len: 0,
owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA,
executable: false,
rent_epoch: 318,
},
),
]
*/
# Note: encoding only available for "base58", "base64" or "base64+zstd"
curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '
{
"jsonrpc": "2.0",
"id": 1,
"method": "getProgramAccounts",
"params": [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
{
"encoding": "base64",
"dataSlice": {
"offset": 0,
"length": 0
},
"filters": [
{
"dataSize": 165
},
{
"memcmp": {
"offset": 0,
"bytes": "BUGuuhPsHpk8YZrL2GctsCtXGneL1gmT5zYb7eMHZDWf"
}
}
]
}
]
}
'
# Output:
# {
# "jsonrpc": "2.0",
# "result": [
# {
# "account": {
# "data": [
# "",
# "base64"
# ],
# "executable": false,
# "lamports": 2039280,
# "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
# "rentEpoch": 313
# },
# "pubkey": "FqWyVSLQgyRWyG1FuUGtHdTQHrEaBzXh1y9K6uPVTRZ4"
# },
# {
# "account": {
# "data": [
# "",
# "base64"
# ],
# "executable": false,
# "lamports": 2039280,
# "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
# "rentEpoch": 314
# },
# "pubkey": "CMSC2GeWDsTPjfnhzCZHEqGRjKseBhrWaC2zNcfQQuGS"
# },
# {
# "account": {
# "data": [
# "",
# "base64"
# ],
# "executable": false,
# "lamports": 2039280,
# "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
# "rentEpoch": 314
# },
# "pubkey": "61NfACb21WvuEzxyiJoxBrivpiLQ79gLBxzFo85BiJ2U"
# },
# {
# "account": {
# "data": [
# "",
# "base64"
# ],
# "executable": false,
# "lamports": 2039280,
# "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
# "rentEpoch": 313
# },
# "pubkey": "Et3bNDxe2wP1yE5ao6mMvUByQUHg8nZTndpJNvfKLdCb"
# }
# ],
# "id": 1
# }
dataSlice
、dataSize
、memcmp
の三つのパラメータをすべて組み合わせることで、クエリの範囲を制限し関心のあるデータのみを効率的に返すことができます。