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 estadoopen in new window
    • (opcional) encoding: string - Codificação para dados da conta: base58, base64, ou jsonParsed. Observe que os usuários do web3js devem usar getParsedProgramAccountsopen in new window
    • (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 conta
      • length: 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ção
        • bytes: 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 RpcResponseopen in new window
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 base58
  • account: object - Um objeto JSON, com os seguintes subcampos:
    • lamports: number, número de lamports atribuídos à conta
    • owner: string, a chave pública codificada em base58 do programa ao qual a conta foi atribuída
    • data: string | object - dados associados à conta, seja como dados binários codificados ou formato JSON, dependendo do parâmetro de codificação fornecido
    • executable: boolean, indicação se a conta contém um programa
    • rentEpoch: 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 SRMopen in new window)
  • Todas as contas personalizadas para um programa específico (ou seja, todos os usuários do Mangoopen in new window)

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 SPLopen in new window. 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 comprimentoopen in new window. 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.

Tamanho da Conta

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.

Tamanho da Conta

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 conta
  • length: 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.

Outros recursos

Last Updated:
Contributors: Daniel Cukier