Kumuha ng Mga Program Accounts

Isang paraan ng RPC na nagbabalik ng lahat ng account na pagmamay-ari ng isang programa. Kasalukuyang hindi sinusuportahan ang pagination. Ang mga kahilingan sa getProgramAccounts ay dapat isama ang mga parameter ng dataSlice at/o filters upang mapahusay ang oras ng pagtugon at magbalik lamang ng mga nilalayong resulta.

Facts

Mga Parameter

  • programId: string - Pubkey ng program na itatanong, na ibinigay bilang base58 na naka-encode na string
  • (opsyonal) configOrCommitment: object - Mga parameter ng configuration na naglalaman ng mga sumusunod na opsyonal na field:
    • (opsyonal) commitment: string - State commitmentopen in new window
    • (opsyonal) encoding: string - Encoding para sa data ng account, alinman sa: base58, base64, o jsonParsed. Tandaan, dapat gamitin ng mga user ng web3js ang getParsedProgramAccountsopen in new window
    • (opsyonal) dataSlice: object - Limitahan ang ibinalik na data ng account batay sa:
      • offset: number - Bilang ng mga byte sa data ng account upang simulan ang pagbabalik
      • length: number - Bilang ng mga byte ng data ng account na ibabalik
    • (opsyonal) mga filter: array - I-filter ang mga resulta gamit ang mga sumusunod na filter object:
      • memcmp: object - Itugma ang isang serye ng mga byte sa data ng account:
        • offset: number - Bilang ng mga byte sa data ng account upang simulan ang paghahambing
        • bytes: string - Data upang tumugma, bilang base58 na naka-encode na string ay limitado sa 129 bytes
      • dataSize: number - Inihahambing ang haba ng data ng account sa ibinigay na laki ng data
    • (opsyonal) withContext: boolean - I-wrap ang resulta sa isang RpcResponse JSON objectopen in new window

Response

Bilang default, magbabalik ang getProgramAccounts ng hanay ng mga JSON object na may sumusunod na istraktura:

  • pubkey: string - Ang account pubkey bilang base58 na naka-encode na string
  • account: object - isang JSON object, na may mga sumusunod na sub field:
    • lamports: number, bilang ng lamports na itinalaga sa account
    • may-ari: string, Ang base58 na naka-encode na pubkey ng program kung saan nakatalaga ang account
    • data: string | object - data na nauugnay sa account, alinman bilang naka-encode na binary data o JSON na format depende sa ibinigay na parameter ng pag-encode
    • executable: boolean, Indikasyon kung ang account ay naglalaman ng program
    • rentEpoch: number, Ang panahon kung saan susunod na uutang ang account na ito

Deep Dive

Ang getProgramAccounts ay isang versatile na paraan ng RPC na ibinabalik ang lahat ng account na pagmamay-ari ng isang program. Maaari nating gamitin ang getProgramAccounts para sa ilang kapaki-pakinabang na query, gaya ng paghahanap ng:

  • Lahat ng token account para sa isang partikular na wallet
  • Lahat ng token account para sa isang partikular na mint (i.e. Lahat ng SRMopen in new window may hawak)
  • Lahat ng custom na account para sa isang partikular na programa (ibig sabihin, Lahat ng Mangoopen in new window user)

Sa kabila ng pagiging kapaki-pakinabang nito, ang getProgramAccounts ay madalas na hindi maintindihan dahil sa mga kasalukuyang hadlang nito. Marami sa mga query na sinusuportahan ng getProgramAccounts ay nangangailangan ng mga RPC node na mag-scan ng malalaking set ng data. Ang mga pag-scan na ito ay parehong memory at resource intensive. Bilang resulta, ang mga tawag na masyadong madalas o masyadong malaki ang saklaw ay maaaring magresulta sa mga timeout ng koneksyon. Higit pa rito, sa oras ng pagsulat na ito, ang endpoint ng getProgramAccounts ay hindi sumusuporta sa pagination. Kung ang mga resulta ng isang query ay masyadong malaki, ang tugon ay puputulin.

Upang malampasan ang mga kasalukuyang hadlang na ito, nag-aalok ang getProgramAccounts ng ilang kapaki-pakinabang na parameter: ibig sabihin, dataSlice at ang mga opsyon sa filters na memcmp at dataSize. Sa pamamagitan ng pagbibigay ng mga kumbinasyon ng mga parameter na ito, maaari nating bawasan ang saklaw ng aming mga query hanggang sa mga mapapamahalaan at mahuhulaan na laki.

Ang isang karaniwang halimbawa ng getProgramAccounts ay kinabibilangan ng pakikipag-ugnayan sa SPL-Token Programopen in new window. Ang paghiling sa lahat ng account na pagmamay-ari ng Token Program na may pangunahing tawag ay magsasangkot ng napakalaking dami ng data. Sa pamamagitan ng pagbibigay ng mga parameter, gayunpaman, maaari nating mahusay na humiling ng data na nilalayon nating gamitin.

mga filter

Ang pinakakaraniwang parameter na gagamitin sa getProgramAccounts ay ang hanay ng filters. Tumatanggap ang array na ito ng dalawang uri ng mga filter, dataSize at memcmp. Bago gamitin ang alinman sa mga filter na ito, dapat na pamilyar tayo sa kung paano inilatag at na-serialize ang data na hinihiling namin.

dataSize

Sa kaso ng Token Program, makikita natin na ang mga token account ay 165 bytes ang habaopen in new window. Sa partikular, ang isang token account ay may walong magkakaibang field, na ang bawat field ay nangangailangan ng predictable na bilang ng mga byte. Maaari nating mailarawan kung paano inilatag ang data na ito gamit ang paglalarawan sa ibaba.

Laki ng Account

Kung gusto nating mahanap ang lahat ng token account na pagmamay-ari ng aming wallet address, maaari nating idagdag ang { dataSize: 165 } sa aming hanay ng filters upang paliitin ang saklaw ng aming query sa mga account na eksaktong 165 bytes ang haba. Ito lamang, gayunpaman, ay hindi sapat. Kakailanganin din nating magdagdag ng filter na naghahanap ng mga account na pagmamay-ari ng aming address. Maaabot natin ito gamit ang filter na memcmp.

memcmp

Ang filter na memcmp, o filter na "paghahambing ng memorya," ay nagbibigay-daan sa amin na paghambingin ang data sa anumang field na nakaimbak sa aming account. Sa partikular, maaari lang kaming mag-query para sa mga account na tumutugma sa isang partikular na hanay ng mga byte sa isang partikular na posisyon. Ang memcmp ay nangangailangan ng dalawang argumento:

  • offset: Ang posisyon kung saan magsisimulang maghambing ng data. Ang posisyon na ito ay sinusukat sa bytes at ipinahayag bilang isang integer.
  • bytes: Ang data na dapat tumugma sa data ng account. Ito ay kinakatawan bilang isang base-58 na naka-encode na string ay dapat na limitado sa mas mababa sa 129 byte.

Mahalagang tandaan na ang memcmp ay magbabalik lamang ng mga resultang eksaktong tugma sa bytes. Sa kasalukuyan, hindi nito sinusuportahan ang mga paghahambing para sa mga value na mas mababa o mas malaki kaysa sa bytes na ibinibigay namin.

Alinsunod sa aming halimbawa ng Token Program, maaari nating baguhin ang aming query upang ibalik lamang ang mga token account na pagmamay-ari ng aming wallet address. Kapag tumitingin sa isang token account, makikita natin ang unang dalawang field na nakaimbak sa isang token account ay parehong mga pubkey, at ang bawat pubkey ay 32 byte ang haba. Dahil ang may-ari ay ang pangalawang field, dapat nating simulan ang ating memcmp sa isang offset na 32 bytes. Mula rito, maghahanap kami ng mga account na ang field ng may-ari ay tumutugma sa aming address ng wallet.

Laki ng Account

Maaari nating tawagan ang query na ito sa pamamagitan ng sumusunod na halimbawa:

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

Sa labas ng dalawang parameter ng filter, ang ikatlong pinakakaraniwang parameter para sa getProgramAccounts ay dataSlice. Hindi tulad ng parameter na filters, hindi babawasan ng dataSlice ang bilang ng mga account na ibinalik ng isang query. Sa halip, lilimitahan ng dataSlice ang dami ng data para sa bawat account.

Katulad ng memcmp, ang dataSlice ay tumatanggap ng dalawang argumento:

  • offset: Ang posisyon (sa bilang ng mga byte) kung saan magsisimulang ibalik ang data ng account
  • length: Ang bilang ng mga byte na dapat ibalik

Partikular na kapaki-pakinabang ang dataSlice kapag nagpapatakbo kami ng mga query sa isang malaking dataset ngunit wala talagang pakialam sa data ng account mismo. Ang isang halimbawa nito ay kung gusto nating hanapin ang bilang ng mga token account (ibig sabihin, bilang ng mga may hawak ng token) para sa isang partikular na token 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
# }

Sa pamamagitan ng pagsasama-sama ng lahat ng tatlong parameter (dataSlice, dataSize, at memcmp) maaari nating limitahan ang saklaw ng aming query at mahusay na ibalik lamang ang data kung saan kami interesado.

Other Resources

Last Updated:
Contributors: mh