Obter Contas do Programa
getProgramAccounts
é um método RPC que retorna todas as contas pertencentes a um programa. Atualmente, a paginação não é suportada. As solicitações para getProgramAccounts
devem incluir os parâmetros dataSlice
e/ou filters
para melhorar o tempo de resposta e retornar apenas os resultados pretendidos.
Fatos
Parâmetros
programId
:string
- A chave pública (pubkey) do programa a ser consultado, fornecida como uma string codificada em base58- (opcional)
configOrCommitment
:object
- Parâmetros de configuração contendo os seguintes campos opcionais:- (opcional)
commitment
:string
- Compromisso de estado - (opcional)
encoding
:string
- Codificação para dados da conta:base58
,base64
, oujsonParsed
. Observe que os usuários do web3js devem usar getParsedProgramAccounts - (opcional)
dataSlice
:object
- Limite os dados da conta retornados com base em:offset
:number
- Número de bytes para começar a retornar os dados da contalength
:number
- Número de bytes dos dados da conta a serem retornados
- (opcional)
filters
:array
- Filtra os resultados usando os seguintes objetos de filtro:memcmp
:object
- Combina uma série de bytes aos dados da conta:offset
:number
- Número de bytes nos dados da conta para iniciar a comparaçãobytes
:string
- Dados para combinar, como uma string codificada em base58 limitada a 129 bytes
dataSize
:number
- Compara o tamanho dos dados da conta com o tamanho de dados fornecido
- (opcional)
withContext
:boolean
- Envolva o resultado em um objeto JSON RpcResponse
- (opcional)
Resposta
Por padrão, getProgramAccounts
retornará um array de objetos JSON com a seguinte estrutura:
pubkey
:string
- A chave pública da conta como uma string codificada em base58account
:object
- Um objeto JSON, com os seguintes subcampos:lamports
:number
, número de lamports atribuídos à contaowner
:string
, a chave pública codificada em base58 do programa ao qual a conta foi atribuídadata
:string
|object
- dados associados à conta, seja como dados binários codificados ou formato JSON, dependendo do parâmetro de codificação fornecidoexecutable
:boolean
, indicação se a conta contém um programarentEpoch
:number
, época na qual esta conta deverá pagar aluguel novamente
Mergulho Profundo
getProgramAccounts
é um método RPC versátil que retorna todas as contas de propriedade de um programa. Podemos usar getProgramAccounts
para várias consultas úteis, como encontrar:
- Todas as contas de token para uma carteira específica
- Todas as contas de token para uma cunhagem de tokens específica (ou seja, todos os detentores de SRM)
- Todas as contas personalizadas para um programa específico (ou seja, todos os usuários do Mango)
Apesar de sua utilidade, getProgramAccounts
é frequentemente mal compreendido devido às suas limitações atuais. Muitas das consultas suportadas pelo getProgramAccounts
exigem que os nós RPC verifiquem grandes conjuntos de dados. Essas verificações são intensivas em recursos e em memória. Como resultado, chamadas muito frequentes ou muito grandes em escopo podem resultar em tempo limite de conexão. Além disso, no momento em que este texto foi escrito, o ponto de extremidade getProgramAccounts
não suporta paginação. Se os resultados de uma consulta forem muito grandes, a resposta será truncada.
Para contornar essas limitações atuais, getProgramAccounts
oferece vários parâmetros úteis, como dataSlice
e as opções de filtros (filters
) memcmp
e dataSize
. Ao fornecer combinações desses parâmetros, podemos reduzir o escopo de nossas consultas a tamanhos gerenciáveis e previsíveis.
Um exemplo comum de getProgramAccounts
envolve a interação com o Programa de Tokens SPL. Solicitar todas as contas de propriedade do Programa de Tokens com uma chamada básica envolveria uma enorme quantidade de dados. No entanto, fornecendo parâmetros, podemos solicitar eficientemente apenas os dados que pretendemos usar.
filters
O parâmetro mais comum a ser usado com getProgramAccounts
é o array filters
. Este array aceita dois tipos de filtros, dataSize
e memcmp
. Antes de usar qualquer um desses filtros, devemos estar familiarizados com a forma como os dados que estamos solicitando são organizados e serializados.
dataSize
No caso do Programa de Tokens, podemos ver que as contas de token têm 165 bytes de comprimento. Especificamente, uma conta de token tem oito campos diferentes, e cada campo requer um número previsível de bytes. Podemos visualizar como esses dados estão organizados usando a ilustração abaixo.
Se quisermos encontrar todas as contas de token de propriedade do nosso endereço de carteira, podemos adicionar {dataSize: 165}
ao nosso array filters
para reduzir o escopo da nossa consulta apenas para as contas que têm exatamente 165 bytes de comprimento. No entanto, isso por si só seria insuficiente. Também precisaríamos adicionar um filtro que procurasse contas de propriedade do nosso endereço. Podemos alcançar isso com o filtro memcmp
.
memcmp
O filtro memcmp
, ou filtro de "comparação de memória", nos permite comparar dados em qualquer campo armazenado em nossa conta. Especificamente, podemos consultar apenas as contas que correspondem a um determinado conjunto de bytes em uma posição específica. memcmp
requer dois argumentos:
offset
: A posição onde iniciar a comparação dos dados. Essa posição é medida em bytes e é expressa como um número inteiro.bytes
: Os dados que devem corresponder aos dados da conta. Isso é representado como uma string codificada em base58 e deve ser limitado a menos de 129 bytes.
É importante observar que memcmp
somente retornará resultados que correspondem exatamente aos bytes
. Atualmente, ele não oferece suporte a comparações para valores menores ou maiores que os bytes
que fornecemos.
Seguindo com nosso exemplo do Programa de Tokens, podemos modificar nossa consulta para retornar somente contas de token que são de propriedade do nosso endereço de carteira. Ao olhar para uma conta de token, podemos ver que os dois primeiros campos armazenados em uma conta de token são chaves públicas e cada chave pública tem 32 bytes de comprimento. Dado que owner
é o segundo campo, devemos iniciar nosso memcmp
em um offset
de 32 bytes. A partir daqui, estaremos procurando por contas cujo campo de proprietário corresponda ao nosso endereço de carteira.
Podemos invocar essa consulta por meio do exemplo a seguir:
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
Fora dos dois parâmetros de filtro, o terceiro parâmetro mais comum para getProgramAccounts
é dataSlice
. Ao contrário do parâmetro filters
, o dataSlice
não reduzirá o número de contas retornadas por uma consulta. Em vez disso, o dataSlice
limitará a quantidade de dados para cada conta.
Assim como o memcmp
, o dataSlice
aceita dois argumentos:
offset
: A posição (em número de bytes) onde iniciar o retorno dos dados da contalength
: O número de bytes que devem ser retornados
O dataSlice
é particularmente útil quando executamos consultas em um grande conjunto de dados, mas na verdade não nos importamos com os dados da conta em si. Um exemplo disso seria se quiséssemos encontrar o número de contas de token (ou seja, número de detentores de token) para uma determinada cunhagem de tokens.
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
# }
Ao combinar os três parâmetros (dataSlice
, dataSize
, e memcmp
), podemos limitar o escopo da nossa consulta e retornar eficientemente apenas os dados que nos interessam.