Mendapatkan Akun Program (getProgramAccounts)

adalah sebuah metode RPC untuk mendapatkan semua akun yang dimiliki oleh suatu program. Saat ini pagination tidak didukung. Request ke getProgramAccounts harus menyertakan parameter dataSlice dan/atau filters untuk mempercepat waktu respon dan hanya mengembalikan hasil yang diinginkan.

Fakta

Parameter

  • programId: string - Pubkey dari program yang akan diambil, disediakan sebagai string yang di encode menggunakan base58
  • (opsional) configOrCommitment: object - Parameter konfigurasi yang berisi field opsional berikut:
    • (opsional) commitment: string - State commitmentopen in new window
    • (opsional) encoding: string - Encoding yang digunakan untuk data akun, baik: base58, base64, atau jsonParsed. Catatan, pengguna web3js sebaiknya menggunakan getParsedProgramAccountsopen in new window
    • (opsional) dataSlice: object - Membatasi jumlah data akun yang dikembalikan berdasarkan:
      • offset: number - Jumlah byte ke dalam data akun untuk mulai kembali
      • length: number - Jumlah byte data akun yang akan dikembalikan
    • (opsional) filters: array - Filter hasil menggunakan objek filter berikut:
      • memcmp: object - Untuk mencocokkan serangkaian byte dengan data akun:
        • offset: number - Posisi byte dalam data akun tempat dimulai perbandingannya
        • bytes: string - Data yang sedang dicocokkan berupa string yang di encode base58, dibatasi hingga 129 byte
      • dataSize: number - Membandingkan panjang data akun dengan ukuran data yang disediakan
    • (opsional) withContext: boolean - Untuk membungkus hasilnya dalam object RpcResponse JSONopen in new window
Response

Secara default getProgramAccounts akan mengembalikan array dari objek JSON dengan struktur berikut:

  • pubkey: string - Pubkey akun berupa string yang diencode base58
  • account: object - sebuah objek JSON, dengan sub-field berikut:
    • lamports: number, jumlah lamport yang dimiliki sebuah akun
    • owner: string, Pubkey dengan encode base58 dari program tempat akun tersebut dipasangkan
    • data: string | object - data yang terkait dengan akun, baik berupa data biner yang telah diencode atau format JSON tergantung pada parameter jenis encoding yang diberikan
    • executable: boolean, untuk mengindikasi jika akun tersebut berisi sebuah program
    • rentEpoch: number, Epoch di mana akun ini selanjutnya akan berutang sewa

Memahami lebih dalam

getProgramAccounts adalah metode RPC serbaguna yang mendapatkan semua akun yang dimiliki oleh suatu program. Kita dapat menggunakan getProgramAccounts untuk sejumlah query yang berguna, seperti menemukan:

Terlepas dari kegunaannya, getProgramAccounts sering disalahpahami karena batasannya saat ini. Banyak query yang didukung oleh getProgramAccounts memerlukan node RPC untuk melakukan scan dari kumpulan data yang besar. Proses scan ini membutuhkan memori dan sumber daya yang intensif. Akibatnya, pemanggilan yang terlalu sering atau terlalu besar cakupannya dapat mengakibatkan connection timeout. Selanjutnya, pada saat penulisan ini, endpoint dari getProgramAccounts tidak mendukung pagination. Jika hasil query terlalu besar, respons akan dipecah (truncate).

Untuk mengatasi kendala saat ini, getProgramAccounts menawarkan sejumlah parameter yang berguna: yaitu, dataSlice dan opsi dari filters yaitu memcmp dan dataSize. Dengan memberikan kombinasi parameter ini, kita dapat mengurangi cakupan query kita menjadi ukuran yang dapat dikelola dan diprediksi.

Contoh umum dari getProgramAccounts melibatkan interaksi dengan Program Token SPLopen in new window. Meminta semua akun yang dimiliki oleh Program Token dengan sebuah basic call akan melibatkan sejumlah data yang besar. Namun, dengan memberikan parameter, kita dapat meminta hanya data yang ingin kita gunakan secara efisien.

filters

Parameter yang paling umum digunakan dengan getProgramAccounts adalah array dari filters. Array ini menerima dua jenis filter, yaitu dataSize dan memcmp. Sebelum menggunakan salah satu dari filter ini, kita harus terbiasa dengan bagaimana data yang kita minta ditata dan diserialisasi.

dataSize

Dalam kasus Program Token, kita dapat melihat bahwa akun token memiliki panjang 165 byteopen in new window. Secara khusus, akun token memiliki delapan field yang berbeda, dengan masing-masing field membutuhkan jumlah byte yang dapat diprediksi. Kita dapat memvisualisasikan bagaimana data ini ditata menggunakan ilustrasi di bawah ini.

Account Size

Jika kita ingin menemukan semua akun token yang dimiliki oleh address wallet kita, kita dapat menambahkan { dataSize: 165 } ke dalam array filters kita untuk memperkecil cakupan query kita menjadi hanya akun yang panjangnya tepat 165 byte. Namun, ini saja tidak akan cukup. kita juga perlu menambahkan filter untuk yang mencari akun yang dimiliki oleh address kita. Kita bisa mendapatkan ini dengan filter memcmp.

memcmp

Filter memcmp, atau filter "memory comparison", memungkinkan kita untuk membandingkan data di field mana pun yang tersimpan di akun kita. Secara khusus, kita hanya dapat melakukan query untuk akun yang cocok dengan sekumpulan byte tertentu pada posisi tertentu. memcmp membutuhkan dua argumen:

  • offset: Posisi untuk mulai membandingkan data. Posisi ini diukur dalam byte dan dinyatakan sebagai bilangan bulat.
  • bytes: Data yang harus cocok dengan data akun. Ini direpresentasikan dengan string yang diencode menggunakan base-58 dan harus berukuran kurang dari 129 byte.

Penting untuk diperhatikan bahwa memcmp hanya akan mengembalikan hasil yang bagian dari datanya sama persis dengan bytes. Saat ini, memcmp tidak mendukung perbandingan untuk data yang kurang dari atau lebih besar dari bytes yang kita berikan.

Sesuai dengan contoh Program Token kita, kita dapat mengubah query kita untuk hanya mengembalikan akun token yang dimiliki oleh address wallet kita. Saat melihat akun token, kita dapat melihat dua field pertama yang disimpan di akun token adalah pubkey, dan masing-masing pubkey memiliki panjang 32 byte. Mengingat bahwa owner adalah field kedua, kita harus memulai memcmp kita pada offset 32 byte. Dari sini, kita akan mencari akun yang field ownernya cocok dengan address wallet kita.

Account Size

kita dapat memanggil query ini melalui contoh berikut:

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

Di luar dua parameter filter, parameter ketiga yang paling umum untuk getProgramAccounts adalah dataSlice. Tidak seperti parameter filters, dataSlice tidak akan mengurangi jumlah akun yang dikembalikan oleh query. Sebagai gantinya, dataSlice akan membatasi jumlah data untuk setiap akun.

Sama seperti memcmp, dataSlice menerima dua argumen:

  • offset: Posisi (dalam jumlah byte) untuk mulai mengambil data akun
  • length: Jumlah byte yang harus dikembalikan

dataSlice sangat berguna saat kita menjalankan query pada kumpulan data yang besar tetapi sebenarnya tidak peduli dengan data akun itu sendiri. Contohnya adalah jika kita ingin menemukan jumlah akun token (yaitu jumlah pemegang token) untuk token mint tertentu.

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

Dengan menggabungkan ketiga parameter (dataSlice, dataSize, dan memcmp), kita dapat membatasi batasan query kita dan hanya mengambil data yang kita perlukan secara efisien.

Resource lainnya

Last Updated:
Contributors: akangaziz