Programmkonten Abfragen

Eine RPC-Methode, die alle Konten zurückgibt, die einem Programm gehören. Paginierung wird derzeit nicht unterstützt. Anfragen an „getProgramAccounts“ sollten die Parameter „dataSlice“ und/oder „filters“ enthalten, um die Antwortzeit zu verbessern und nur beabsichtigte Ergebnisse zurückzugeben.

Fakten

Parameters

  • „programId“: „string“ – Pubkey des abzufragenden Programms, bereitgestellt als base58-codierter String
  • (optional) configOrCommitment: object - Konfigurationsparameter, die die folgenden optionalen Felder enthalten:
    • (optional) commitment: string - Staatliche Verpflichtungopen in new window
    • (optional) encoding: string - Encoding für Kontodaten, entweder: base58, base64 oder jsonParsed. Beachten Sie, dass Benutzer von web3js stattdessen getParsedProgramAccountsopen in new window verwenden sollten.
    • (optional) dataSlice: object - Beschränken Sie die zurückgegebenen Kontodaten basierend auf:
      • offset: number - Anzahl der Bytes in Kontodaten, um mit der Rückgabe zu beginnen
      • length: number - Anzahl der zurückzugebenden Kontodaten-Bytes
    • (optional) filters: array - Filtern Sie die Ergebnisse mit den folgenden Filterobjekten:
      • memcmp: object - Passen Sie eine Reihe von Bytes an Kontodaten an:
        • offset: number - Anzahl der Bytes in den Kontodaten, um mit dem Vergleich zu beginnen
        • bytes: string - Zu vergleichende Daten, als Base58-codierter String, begrenzt auf 129 Bytes
      • dataSize: number - Vergleicht die Kontodatenlänge mit der bereitgestellten Datengröße
    • (optional) withContext: boolean - Das Ergebnis in ein [RpcResponse JSON-Objekt] einschließen (https://docs.solana.com/developing/clients/jsonrpc-api#rpcresponse-structure)
Antwort

Standardmäßig gibt getProgramAccounts ein Array von JSON-Objekten mit der folgenden Struktur zurück:

  • pubkey: string - Der Konto-Pubkey als base58-codierter String
  • „account“: „object“ – ein JSON-Objekt mit den folgenden Unterfeldern:
    • lamports: number, Anzahl der dem Konto zugeordneten Lamports
    • owner: string, Der base58-kodierte Pubkey des Programms, dem das Konto zugewiesen wurde
    • „Daten“: „Zeichenfolge“ | „Objekt“ – Daten, die dem Konto zugeordnet sind, entweder als codierte Binärdaten oder im JSON-Format, abhängig vom bereitgestellten Codierungsparameter
    • executable: boolean, Angabe ob das Konto ein Programm enthält
    • rentEpoch: number, Die Epoche, in der dieses Konto das nächste mal Miete schuldet :::

Deep Dive

„getProgramAccounts“ ist eine vielseitige RPC-Methode, die alle Konten zurückgibt, die einem Programm gehören. Wir können "getProgramAccounts" für eine Reihe nützlicher Abfragen verwenden, z. B. um Folgendes zu finden:

  • Alle Token-Konten für eine bestimmte Brieftasche
  • Alle Token-Konten für eine bestimmte Minze (d. h. alle SRMopen in new window-Inhaber)
  • Alle benutzerdefinierten Konten für ein bestimmtes Programm (d. h. alle Benutzer von Mangoopen in new window)

Trotz seiner Nützlichkeit wird getProgramAccounts aufgrund seiner derzeitigen Beschränkungen oft missverstanden. Viele der von „getProgramAccounts“ unterstützten Abfragen erfordern RPC-Knoten, um große Datensätze zu scannen. Diese Scans sind sowohl speicher- als auch ressourcenintensiv. Daher können zu häufige oder zu umfangreiche Aufrufe zu Verbindungszeitüberschreitungen führen. Darüber hinaus unterstützt der Endpunkt „getProgramAccounts“ zum Zeitpunkt der Erstellung dieses Dokuments keine Paginierung. Wenn die Ergebnisse einer Abfrage zu groß sind, wird die Antwort abgeschnitten.

Um diese derzeitigen Beschränkungen zu umgehen, bietet getProgramAccounts eine Reihe nützlicher Parameter: nämlich dataSlice und die filters-Optionen memcmp und dataSize. Durch die Bereitstellung von Kombinationen dieser Parameter können wir den Umfang unserer Abfragen auf überschaubare und vorhersehbare Größen reduzieren.

Ein gängiges Beispiel für „getProgramAccounts“ ist die Interaktion mit dem [SPL-Token-Programm] (https://spl.solana.com/token). Das Anfordern aller Konten des Token-Programms mit einem einfachen Aufruf würde eine enorme Datenmenge erfordern. Durch die Bereitstellung von Parametern können wir jedoch effizient nur die Daten anfordern, die wir verwenden möchten.

filters

Der häufigste Parameter, der mit „getProgramAccounts“ verwendet wird, ist das „filters“-Array. Dieses Array akzeptiert zwei Arten von Filtern, „dataSize“ und „memcmp“. Bevor Sie einen dieser Filter verwenden, sollten wir uns damit vertraut machen, wie die angeforderten Daten angeordnet und serialisiert sind.

dataSize

Im Fall des Token-Programms können wir sehen, dass Token-Konten 165 Byte lang sindopen in new window. Insbesondere hat ein Token-Konto acht verschiedene Felder, wobei jedes Feld eine vorhersagbare Anzahl von Bytes erfordert. Wir können anhand der folgenden Abbildung visualisieren, wie diese Daten angeordnet sind.

Account Größe

Wenn wir alle Token-Konten finden möchten, die unserer Wallet-Adresse gehören, könnten wir „{ dataSize: 165 }“ zu unserem „filters“-Array hinzufügen, um den Umfang unserer Abfrage auf nur Konten einzugrenzen, die genau 165 Byte lang sind. Dies allein würde jedoch nicht ausreichen. Wir müssten auch einen Filter hinzufügen, der nach Konten sucht, die unserer Adresse gehören. Dies können wir mit dem memcmp-Filter erreichen.

memcmp

Der memcmp-Filter oder "Speichervergleichsfilter" ermöglicht es uns, Daten in jedem Feld zu vergleichen, das in unserem Konto gespeichert ist. Insbesondere können wir nur nach Konten abfragen, die mit einem bestimmten Satz von Bytes an einer bestimmten Position übereinstimmen. memcmp erfordert zwei Argumente:

  • offset: Die Position, an der mit dem Datenvergleich begonnen werden soll. Diese Position wird in Bytes gemessen und als ganze Zahl ausgedrückt.
  • bytes: Die Daten, die mit den Daten des Kontos übereinstimmen sollen. Dies wird als Base-58-codierte Zeichenfolge dargestellt und sollte auf weniger als 129 Bytes begrenzt sein.

Es ist wichtig zu beachten, dass "memcmp" nur Ergebnisse zurückgibt, die eine genaue Übereinstimmung mit "Bytes" sind. Derzeit werden keine Vergleiche für Werte unterstützt, die kleiner oder größer als die von uns bereitgestellten „Bytes“ sind.

In Übereinstimmung mit unserem Beispiel für das Token-Programm können wir unsere Abfrage so ändern, dass nur Token-Konten zurückgegeben werden, die unserer Wallet-Adresse gehören. Wenn wir uns ein Token-Konto ansehen, können wir sehen, dass die ersten beiden Felder, die auf einem Token-Konto gespeichert sind, beide Pubkeys sind und dass jeder Pubkey 32 Bytes lang ist. Da „Eigentümer“ das zweite Feld ist, sollten wir unser „memcmp“ bei einem „Offset“ von 32 Bytes beginnen. Von hier aus suchen wir nach Konten, deren Eigentümerfeld mit unserer Brieftaschenadresse übereinstimmt.

Account Größe

Wir können diese Abfrage über das folgende Beispiel aufrufen:

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 {
          offset: 32,
          bytes: MemcmpEncodedBytes::Base58(MY_WALLET_ADDRESS.to_string()),
          encoding: Some(MemcmpEncoding::Binary),
      }),
      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

Abgesehen von den beiden Filterparametern ist der dritthäufigste Parameter für „getProgramAccounts“ „dataSlice“. Im Gegensatz zum Parameter "filters" reduziert "dataSlice" nicht die Anzahl der von einer Abfrage zurückgegebenen Konten. Stattdessen begrenzt „dataSlice“ die Datenmenge für jedes Konto.

Ähnlich wie memcmp akzeptiert dataSlice zwei Argumente:

  • offset: Die Position (in Bytes), an der mit der Rückgabe von Kontodaten begonnen werden soll
  • length: Die Anzahl der Bytes, die zurückgegeben werden sollen

dataSliceist besonders nützlich, wenn wir Abfragen für einen großen Datensatz ausführen, uns aber nicht um die Kontodaten selbst kümmern. Ein Beispiel dafür wäre, wenn wir die Anzahl der Token-Konten (d. h. die Anzahl der Token-Inhaber) für eine bestimmte Token-Münze ermitteln wollten.

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 {
          offset: 0, // number of bytes
          bytes: MemcmpEncodedBytes::Base58(MY_TOKEN_MINT_ADDRESS.to_string()),
          encoding: Some(MemcmpEncoding::Binary),
      }),
      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 the empty <Buffer > at acccount.data)

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

Durch die Kombination aller drei Parameter (dataSlice, dataSize und memcmp) können wir den Umfang unserer Abfrage begrenzen und effizient nur die Daten zurückgeben, an denen wir interessiert sind.

Other Resources

Last Updated:
Contributors: nyk