# API de saldos de tokens de carteira: ativos ERC-20 e histórico de transferências

> Source: https://docs.blockvectra.com/pt-br/guides/wallet-assets/

Crie uma página de ativos de carteira com a API de dados de carteiras blockchain: use a [API de saldos de tokens](https://blockvectra.com/en/data/balances/) para posições ERC-20 diferentes de zero e a [API de transferências de tokens](https://blockvectra.com/en/data/transfers/) para o histórico da carteira. Desenvolvedores e agentes de IA usam as mesmas requisições autenticadas. Antes de consultar, leia [GET /v1/status](https://api.blockvectra.com/v1/data) e verifique `data_features` e `data_status` da rede selecionada; a cobertura de saldos varia por rede. Os parâmetros de requisição e esquemas de resposta estão na [referência da Data API](https://docs.blockvectra.com/en/api/data/).

## Tarefas que este guia ajuda a concluir

* [Consultar saldos de tokens da carteira](#request-1-address-balances) com uma API key e paginar posições ERC-20 diferentes de zero.
* [Consultar o histórico de transferências da carteira](#request-2-address-transfers) em uma janela fixa de blocos e seguir os cursores do endereço selecionado.
* [Completar metadados de tokens](#request-3-token-metadata-and-tokensbatch) para exibir nomes e símbolos junto dos saldos inteiros brutos, preservando campos ausentes.

## Os três tipos de dados de uma página de ativos de carteira

Uma página de ativos de carteira pode exibir os saldos de tokens ERC-20, o histórico de transferências de tokens e os metadados de tokens de um endereço. A Data API oferece um endpoint para cada tipo:

* **Saldos**: `GET /{chain}/addresses/{address}/balances` retorna os saldos ERC-20 diferentes de zero do endereço, ordenados por endereço `token` em ordem crescente, com `symbol` e `decimals` do token quando disponíveis. Um endereço sem saldos retorna `200` com `data: []`.
* **Transferências**: `GET /{chain}/addresses/{address}/transfers` retorna transferências de tokens envolvendo o endereço dentro de uma janela de blocos obrigatória, ordenadas por `(block_number, log_index)` em ordem decrescente.
* **Metadados de tokens**: `GET /{chain}/tokens/{token}` consulta nome, símbolo, casas decimais e oferta total de um token pelo endereço do contrato; `POST /{chain}/tokens:batch` consulta os mesmos metadados para até 100 endereços em uma requisição.

Os três usam `https://api.blockvectra.com/v1/data` como URL base e o cabeçalho de requisição `x-api-key`, com `robinhood_mainnet` como rede de exemplo. Pertencem aos recursos `balances`, `transfers` e `token_metadata`, respectivamente; para saber quais redes oferecem cada recurso, consulte [Redes compatíveis](https://docs.blockvectra.com/en/chains/). Em uma rede sem o recurso, o endpoint retorna `422 no_coverage`.

## Requisição 1: saldos do endereço

Este endpoint recebe menos parâmetros, por isso é uma boa primeira requisição para uma página:

* `{chain}` (parâmetro de caminho, obrigatório): identificador da rede, o valor `chain` de uma entrada de `GET /chains` (por exemplo, `robinhood_mainnet`). A correspondência é exata e diferencia maiúsculas e minúsculas; aliases e Chain IDs numéricos não são aceitos.
* `{address}` (parâmetro de caminho, obrigatório): endereço de 20 bytes; o prefixo `0x` é opcional e maiúsculas e minúsculas são aceitas.
* `limit` (parâmetro de consulta, opcional): tamanho da página. O padrão é 50; valores acima de 500 são limitados a 500; `0` ou um valor não inteiro retorna `400 bad_request`.
* `cursor` (parâmetro de consulta, opcional): o `next_cursor` da resposta anterior, enviado sem alterações para obter a próxima página. Um cursor é válido apenas para a rede, o endpoint e os parâmetros de consulta que o emitiram; reutilizá-lo em outro contexto retorna `400 bad_request`.

A paginação é por chave: `next_cursor` aparece apenas quando há outra página. Na última página, a chave fica totalmente ausente, nunca `null`.

**cURL**

```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key

curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances?limit=50" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```


  **TypeScript**

```ts
const address = "0x1111111111111111111111111111111111111111";
const url = new URL(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
);
url.searchParams.set("limit", "50");

const res = await fetch(url, {
  headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const balanceBody = await res.json();
console.log(balanceBody.data, balanceBody.meta);

// npx tsx example.mts
```


  **Python**

```python
import os
import requests

address = "0x1111111111111111111111111111111111111111"
res = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/balances",
    params={"limit": 50},
    headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
res.raise_for_status()
balance_body = res.json()
print(balance_body["data"], balance_body["meta"])
```


O envelope de resposta é `AddressBalanceListEnvelope`, com `data` e `meta`. Cada item de `data` é um `AddressBalance`:

| Campo      | Tipo                | Descrição                                                                                                                                            |
| ---------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`    | `string` (endereço) | Endereço do contrato do token; a forma canônica é `0x` seguido de 40 dígitos hexadecimais em letras minúsculas.                                      |
| `balance`  | `string` (decimal)  | Saldo inteiro bruto, que pode ultrapassar `2^53`, retornado como string decimal simples — nunca como número JSON, notação científica ou hexadecimal. |
| `symbol`   | `string` ou `null`  | Símbolo do token ou `null` quando indisponível.                                                                                                      |
| `decimals` | `integer` ou `null` | Casas decimais do token, `0`–`255`, ou `null` quando indisponíveis.                                                                                  |

## Requisição 2: transferências do endereço

O endpoint de transferências exige uma janela explícita de blocos: `from_block` e `to_block` são obrigatórios e devem satisfazer `from_block <= to_block`. Ele recebe mais alguns parâmetros:

* `standard` (parâmetro de consulta, obrigatório): `erc20` ou `erc721`. Consultas por endereço não cobrem `erc1155`; enviá-lo retorna `422 no_coverage`.
* `direction` (parâmetro de consulta, opcional): `in`, `out` ou `any`; o padrão é `any` e filtra pela direção em relação ao endereço.
* `token` (parâmetro de consulta, opcional): restringe resultados a um contrato de token.
* `clamp` (parâmetro de consulta, opcional): apenas a string literal `true` o habilita; qualquer outro valor é tratado como `false`.

Limites da janela e finalidade: um `to_block` explícito acima de `as_of_block` retorna `409 not_indexed_yet`, a menos que `clamp=true` o reduza a `as_of_block`; uma janela maior que o limite da rede (`limits.max_window_blocks` de `GET /chains`) retorna `409 window_too_large`, a menos que `clamp=true` a reduza pelo lado mais antigo (aumentando `from_block` e mantendo `to_block` fixo). Se o próprio `from_block` já estiver acima de `as_of_block`, continua sendo um `409` obrigatório, mesmo com `clamp=true`. Quando a janela é reduzida ou parcialmente coberta, `meta.coverage` da resposta é `"partial"`; caso contrário, é `"full"`.

Nos registros de transferências, itens ERC-20 acrescentam `amount`; itens ERC-721 acrescentam `token_id`. Ambos incluem `token`, `standard`, `from`, `to`, `block_number`, `block_timestamp`, `tx_hash`, `tx_index` e `log_index`.

**cURL**

```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key

# 1) Read as_of_block from the balances response.
AS_OF_BLOCK=$(curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" | grep -o '"as_of_block":[0-9]*' | cut -d: -f2)

# 2) clamp=true truncates a too-wide window, or a to_block above as_of_block, instead of returning 409.
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/transfers?standard=erc20&from_block=0&to_block=$AS_OF_BLOCK&direction=any&clamp=true" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```


  **TypeScript**

```ts
const address = "0x1111111111111111111111111111111111111111";

// 1) Read as_of_block from any previous response's meta.
const head = await fetch(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());

// 2) Use as_of_block as the transfer window's upper bound.
const url = new URL(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/transfers`,
);
url.searchParams.set("standard", "erc20");
url.searchParams.set("from_block", "0");
url.searchParams.set("to_block", String(head.meta.as_of_block));
url.searchParams.set("direction", "any");
url.searchParams.set("clamp", "true");

const res = await fetch(url, {
  headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const body = await res.json();
console.log(body.data, body.meta);

// npx tsx example.mts
```


  **Python**

```python
import os
import requests

address = "0x1111111111111111111111111111111111111111"
headers = {"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]}

# 1) Read as_of_block from any previous response's meta.
head = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/balances",
    headers=headers,
).json()

# 2) Use as_of_block as the transfer window's upper bound.
res = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/transfers",
    params={
        "standard": "erc20",
        "from_block": 0,
        "to_block": head["meta"]["as_of_block"],
        "direction": "any",
        "clamp": "true",
    },
    headers=headers,
)
res.raise_for_status()
body = res.json()
print(body["data"], body["meta"])
```


## Paginar todas as transferências

O `next_cursor` do endpoint de transferências de endereços é otimista: aparece apenas quando a página retornou exatamente `limit` linhas, portanto uma página pode ter `next_cursor` e ainda ser a última. Não pare quando uma página estiver vazia; siga `next_cursor` até que a chave esteja ausente.

O código abaixo obtém todas as transferências da janela:

**TypeScript**

```ts
const address = "0x1111111111111111111111111111111111111111";
const head = await fetch(
  `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/balances`,
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());
const asOfBlock = head.meta.as_of_block;
const transfers: unknown[] = [];
let cursor: string | undefined;

do {
  const url = new URL(
    `https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/${address}/transfers`,
  );
  url.searchParams.set("standard", "erc20");
  url.searchParams.set("from_block", "0");
  url.searchParams.set("to_block", String(asOfBlock));
  url.searchParams.set("limit", "500");
  // clamp truncates from the older end
  url.searchParams.set("clamp", "true");
  if (cursor) url.searchParams.set("cursor", cursor);

  const res = await fetch(url, {
    headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
  });
  const page = await res.json();
  transfers.push(...page.data);
  cursor = page.next_cursor; // absent on the last page
} while (cursor);
```


  **Python**

```python
import os
import requests

address = "0x1111111111111111111111111111111111111111"
head = requests.get(
    f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/balances",
    headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
).json()
as_of_block = head["meta"]["as_of_block"]
transfers = []
cursor = None

while True:
    params = {
        "standard": "erc20",
        "from_block": 0,
        "to_block": as_of_block,
        "limit": 500,
        # clamp truncates from the older end
        "clamp": "true",
    }
    if cursor:
        params["cursor"] = cursor
    res = requests.get(
        f"https://api.blockvectra.com/v1/data/robinhood_mainnet/addresses/{address}/transfers",
        params=params,
        headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
    )
    res.raise_for_status()
    page = res.json()
    transfers.extend(page["data"])
    cursor = page.get("next_cursor")  # absent on the last page
    if not cursor:
        break
```


## Requisição 3: metadados de tokens e tokens:batch

Consulte um token com `GET /{chain}/tokens/{token}`; o caminho recebe apenas `{chain}` e `{token}`, sem paginação. O envelope da resposta é `TokenEnvelope`, e `data` é um `Token`:

| Campo                 | Tipo                 | Descrição                                                                                                                                                                   |
| --------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`             | `string` (endereço)  | Endereço do contrato do token.                                                                                                                                              |
| `standard`            | `string`             | `erc20`, `erc721` ou `unknown`.                                                                                                                                             |
| `name`                | `string` ou `null`   | Nome do token ou `null` quando indisponível.                                                                                                                                |
| `symbol`              | `string` ou `null`   | Símbolo do token ou `null` quando indisponível.                                                                                                                             |
| `decimals`            | `integer` ou `null`  | Casas decimais do token, `0`–`255`, ou `null` quando indisponíveis.                                                                                                         |
| `total_supply`        | `string` ou `null`   | Oferta total bruta; a API não aplica escala por `decimals`. `null` quando indisponível.                                                                                     |
| `first_seen_block`    | `integer` (int64)    | Altura do bloco em que o token foi visto pela primeira vez.                                                                                                                 |
| `metadata_updated_at` | `string` (timestamp) | Horário UTC da última atualização dos metadados.                                                                                                                            |
| `metadata_block`      | `integer` (int64)    | Altura do bloco em que os metadados foram lidos.                                                                                                                            |
| `metadata_status`     | `string`             | `ok`, `partial` ou `unavailable`.                                                                                                                                           |
| `metadata_issues`     | `object`             | Registros de problemas por campo, com chaves `name`, `symbol`, `decimals`, `total_supply` e valores `reverted`, `no_data`, `invalid_encoding` ou `temporarily_unavailable`. |

Um `{token}` que não seja um endereço válido de 20 bytes retorna `400 bad_request`; um `{token}` desconhecido retorna `404 not_found`; um `{chain}` desconhecido retorna `404 unknown_chain`.

O endpoint de saldos já inclui `symbol` e `decimals` quando disponíveis, mas ambos podem ser `null`. Para completar o nome e as casas decimais de cada token em uma carteira, use `POST /{chain}/tokens:batch`:

* O corpo da requisição é `{"addresses": [...]}` com no máximo 100 endereços por requisição; mais de 100 entradas ou uma entrada que não seja um endereço válido de 20 bytes retorna `400 bad_request` (falha no primeiro endereço inválido encontrado).
* Endereços não encontrados não geram erro; são listados em `data.missing`, enquanto `data.tokens` contém apenas os tokens cujos metadados foram encontrados.
* Endereços duplicados são deduplicados em `tokens` e `missing`, ambos na ordem da primeira ocorrência na requisição.

**cURL**

```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key

# Single token
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

# Batch: up to 100 addresses per request
curl -s -X POST "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"addresses":["0x1111111111111111111111111111111111111111","0x2222222222222222222222222222222222222222"]}'
```


  **TypeScript**

```ts
// Single token
const single = await fetch(
  "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111",
  { headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
).then((r) => r.json());
console.log(single.data);

// Batch: group by 100 addresses to enrich the tokens from the balances response
const BATCH_SIZE = 100;
const addresses = balanceBody.data.map((item: { token: string }) => item.token);
const tokens = new Map<string, unknown>();
const missing: string[] = [];

for (let i = 0; i < addresses.length; i += BATCH_SIZE) {
  const res = await fetch("https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.BLOCKVECTRA_API_KEY!,
    },
    body: JSON.stringify({ addresses: addresses.slice(i, i + BATCH_SIZE) }),
  });
  const body = await res.json();
  for (const token of body.data.tokens) tokens.set(token.address, token);
  missing.push(...body.data.missing);
}

// npx tsx example.mts
```


  **Python**

```python
import os
import requests

headers = {"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]}

# Single token
single = requests.get(
    "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111",
    headers=headers,
).json()
print(single["data"])

# Batch: group by 100 addresses to enrich the tokens from the balances response
BATCH_SIZE = 100
addresses = [item["token"] for item in balance_body["data"]]
tokens = {}
missing = []

for i in range(0, len(addresses), BATCH_SIZE):
    res = requests.post(
        "https://api.blockvectra.com/v1/data/robinhood_mainnet/tokens:batch",
        json={"addresses": addresses[i : i + BATCH_SIZE]},
        headers={**headers, "Content-Type": "application/json"},
    )
    res.raise_for_status()
    body = res.json()
    for token in body["data"]["tokens"]:
        tokens[token["address"]] = token
    missing.extend(body["data"]["missing"])
```


## Ajustar quantidades pelas casas decimais

O campo de saldo `balance` e o campo de transferência ERC-20 `amount` são inteiros brutos representados como strings decimais (`UInt256String`); o `total_supply` de um token também é um inteiro bruto on-chain sem escala por `decimals`. Para exibir uma quantidade legível, divida pelas casas decimais `decimals` desse token.

* `decimals` vem dos campos `symbol`/`decimals` do próprio item de saldo ou de `GET /{chain}/tokens/{token}` e `POST /{chain}/tokens:batch`; pode ser `null`.
* Esses valores podem ultrapassar `2^53`, portanto não faça os cálculos com um número JSON: use `BigInt` em TypeScript e `Decimal` em Python, interpretando a string decimal sem alterações para evitar perda de precisão.

**TypeScript**

```ts
function toDisplayAmount(raw: string, decimals: number | null): string {
  if (decimals === null) return raw; // no decimals metadata: keep the raw integer
  const value = BigInt(raw);
  const base = 10n ** BigInt(decimals);
  const whole = value / base;
  const fraction = (value % base)
    .toString()
    .padStart(decimals, "0")
    .replace(/0+$/, "");
  return fraction ? `${whole}.${fraction}` : whole.toString();
}

// balance.balance is a raw decimal string; decimals comes from the same item or tokens:batch.
const display = toDisplayAmount(balance.balance, balance.decimals);
```


  **Python**

```python
from decimal import Decimal


def to_display_amount(raw: str, decimals: int | None) -> str:
    if decimals is None:
        return raw  # no decimals metadata: keep the raw integer
    value = Decimal(raw)  # parse the decimal string exactly
    return format(value.scaleb(-decimals).normalize(), "f")


# balance["balance"] is a raw decimal string; decimals comes from the same item or tokens:batch.
display = to_display_amount(balance["balance"], balance["decimals"])
```


## Atualização dos dados

Toda resposta bem-sucedida específica de uma rede contém `meta`:

* `as_of_block`: o bloco mais recente da rede completamente gravado. Endpoints por bloco fornecem dados até essa altura.
* `safe_block`: marcador da tag de bloco de consenso `safe` do nó (`null` enquanto desconhecido). Nunca abaixo de `finalized_block` e não reduz, rejeita ou atrasa respostas.
* `finalized_block`: marcador da tag de bloco de consenso `finalized` do nó (`null` enquanto desconhecido). Não reduz, rejeita ou atrasa respostas; os clientes decidem a segurança necessária com base no marcador (como status de confirmação).
* `coverage`: `"full"` ou `"partial"`. Transferências de endereços e endpoints semelhantes informam `"partial"` quando `clamp` reduziu a janela atendida ou quando a janela começa antes do primeiro bloco indexado da rede.
* `refreshed_at`: quando os dados da resposta foram atualizados pela última vez (UTC). Pode ser `null`: `null` significa que o horário de atualização é desconhecido e os dados devem ser tratados como desatualizados; endpoints baseados em blocos sempre retornam um valor.
* Também repete `chain`, `chain_slug` e `chain_external_id`.

Um padrão comum: leia `meta.as_of_block` de qualquer primeira resposta para consultar até o bloco mais recente indexado e verifique `meta.safe_block` / `meta.finalized_block` se quiser exibir o status de confirmação.

## Estimativa de CU para uma carga da página

Cada método é cobrado pelo seu peso de CU, consultado na API de planos da plataforma:

**Peso de CU por chamada**

| Método | CU por chamada |
| --- | --- |
| `data.address_balances` | 25 |
| `data.address_transfers` | 25 |
| `data.tokens_batch` | 10 |

**Uma carga da página (estimativa)**

1 requisição de saldos + 3 páginas de transferências + 1 requisição(ões) `tokens:batch`, 5 chamadas no total, cerca de 110 CU. O uso real depende da quantidade de páginas e tokens.

Para critérios de cobrança e respostas de erro sem cobrança, consulte as [regras de cobrança](https://docs.blockvectra.com/en/guides/billing-rules/). Se você precisa de logs dos blocos mais recentes em vez de histórico de transferências indexado, leia primeiro [Dados recentes do nó e histórico indexado](https://docs.blockvectra.com/en/guides/logs-vs-transfers/) antes de decidir se deve usar `eth_getLogs`.

## Próximos passos

* [Explore o diretório de conjuntos de dados](https://blockvectra.com/en/data/) para ver todos os conjuntos de dados indexados pela BlockVectra.
* [Consulte o plano gratuito e os preços](https://blockvectra.com/en/pricing/#free) para verificar o que sua conta inclui.
* [Entre no console](https://console.blockvectra.com/login/?next=%2Fkeys%2F) para criar uma API key.
