Get Program Accounts

プログラムに所有されるアカウントすべてを返すRPCメソッド。ページネーションはサポートしていません。 レスポンス向上をしつつ意図した結果のみを返すためには、 getProgramAccounts にはdataSlicefilters、あるいはその両方をパラメータに含める必要があります。

概要

Parameters

  • programId: string - base58 でエンコードされた文字列として提供される、クエリするプログラムの公開鍵
  • (optional) configOrCommitment: object - 次のオプションフィールドを含む設定パラメータ:
    • (optional) commitment: string - State commitmentopen in new window
    • (optional) encoding: string - アカウント データのエンコード: base58base64jsonParsed のいずれか。 ※ web3js ユーザーは代わりにgetParsedProgramAccountsopen in new windowを使用する必要があります。
    • (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 objectopen in new windowにラップするかどうか。
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のすべてのトークン アカウント ( つまり、SRMopen in new window の所有者すべて)
  • 特定のプログラムのすべてのカスタム アカウント (つまり、Mangoopen in new window ユーザー全員)

getProgramAccountsは非常に便利ですが、現在の制約のためによく誤解されます。 サポートされているクエリの多くは、大量のデータ セットをスキャンするためにRPC ノードを必要とします。 これらのスキャンは、メモリとリソースの両方を集中的に使用します。その結果、呼び出しの頻度が高すぎたり取得範囲が大きすぎたりすると、 接続タイムアウトが発生する可能性があります。 さらに、この記事の執筆時点では、getProgramAccountsエンドポイントはページネーションをサポートしていません。 クエリの結果が大きすぎる場合、レスポンスは破棄されます。

これらの現在の制約を回避するために、dataSlicefiltersmemcmpdataSizeなどの有用なパラメータが提供されています。 これらのパラメーターの組み合わせにより、クエリの範囲を予測可能なサイズに縮小できます。

getProgramAccountsの一般的な例として、SPL-Token Programopen in new windowとの対話があります。 basic callでトークン プログラムが所有するすべてのアカウントを要求すると、膨大な量のデータが必要になります。 ただし、パラメーターを指定することで、使用するデータのみを効率的に取得できます。

filters

getProgramAccounts 使用する最も一般的なパラメーターは、 filters 配列です。 dataSizememcmpの 2 種類のフィルターを受け入れます。 これらのフィルターのいずれかを使用する前に、要求しているデータがどのように配置され、シリアル化されるかを理解する必要があります。

dataSize

トークン プログラムの場合、トークン アカウントの長さは 165 バイトopen in new windowです。 具体的には、トークンアカウントには8つの異なるフィールドがあり、各フィールドには予測可能なバイト数が必要です。 以下の図を使用して、このデータがどのように配置されているかを視覚化できます。

Account Size

ウォレットアドレスが所有するすべてのトークンアカウントを検索する場合は、filters{ dataSize: 165 } を追加することで、クエリの範囲を正確に165 バイトの長さのアカウントだけに絞り込むことができます。ただし、これだけでは不十分です。 アドレスが所有するアカウントを検索するフィルターも追加する必要があります。これは memcmp フィルターで実現できます。

memcmp

memcmp、または "memory comparison"フィルターを使用すると、アカウントに保存されている任意のフィールドのデータを比較できます。 具体的には、特定の位置にある特定のバイト セットに一致するアカウントのみを照会できます。 memcmpには 2 つの引数が必要です:

  • offset: データの比較を開始する位置。この位置はバイト単位で測定され、整数として表されます。
  • bytes: アカウントのデータと一致させるデータ。これは、base-58エンコードの文字列で、129 バイト未満に制限する必要があります。

memcmpbytesが完全に一致する結果のみを返すことに注意してください。 現在は、指定したbytesより大きい、または小さい値の比較はサポートされていません。

トークン プログラムの例に沿って、クエリを修正して、ウォレット アドレスが所有するトークン アカウントのみを返すことができます。トークン アカウントを見ると、トークン アカウントに保存されている最初の 2 つのフィールドが両方とも公開鍵であり、各公開鍵の長さが 32 バイトであることがわかります。ownerが 2 番目のフィールドであることを考えると、 memcmp は 32バイトのoffsetで開始する必要があります。ここから、所有者フィールドがウォレット アドレスと一致するアカウントを探します。

Account Size

次の例を使用して、このクエリを呼び出すことができます:

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
# }

dataSlicedataSizememcmpの三つのパラメータをすべて組み合わせることで、クエリの範囲を制限し関心のあるデータのみを効率的に返すことができます。

その他参考資料

Last Updated:
Contributors: PokoPoko2ry