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 Verpflichtung - (optional)
encoding
:string
- Encoding für Kontodaten, entweder:base58
,base64
oderjsonParsed
. Beachten Sie, dass Benutzer von web3js stattdessen getParsedProgramAccounts 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 beginnenlength
: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 beginnenbytes
: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)
- (optional)
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 Lamportsowner
: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ältrentEpoch
: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 SRM-Inhaber)
- Alle benutzerdefinierten Konten für ein bestimmtes Programm (d. h. alle Benutzer von Mango)
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 sind. 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.
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.
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 solllength
: Die Anzahl der Bytes, die zurückgegeben werden sollen
dataSlice
ist 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.