# Como monitorar pagamentos em USDT / USDC com Webhooks e RPC

> Source: https://docs.blockvectra.com/pt-br/guides/stablecoin-payments/

Para monitorar pagamentos com stablecoins ou detectar depósitos em exchanges, monitore transferências ERC-20 recebidas de USDT / USDC em redes EVM usando Webhooks, logs via WebSocket ou polling HTTP. Desenvolvedores e agentes de IA usam as mesmas APIs; selecione a rede, o contrato do token, o destinatário e a profundidade de confirmação antes de processar pagamentos. Escolha um fluxo de depósito, notificação para lojistas ou pagamento na [solução de monitoramento de transferências USDT / USDC](https://blockvectra.com/en/use-cases/stablecoin-payments/).

O monitoramento básico de transferências está disponível. A filtragem por quantidade e token é executada no seu receptor. Condições no servidor, múltiplas etapas de confirmação e alertas por mensagens instantâneas estarão disponíveis em breve.

Para desenvolvedores e agentes de IA: comece com uma [API key](https://console.blockvectra.com/login/?next=%2Fkeys%2F) e seu próprio receptor HTTPS; filtre contratos de tokens e quantidades na sua aplicação. [Copie a configuração de Webhook](#create-a-subscription-and-watch-the-recipient).

## Tarefas que este guia ajuda a concluir

* [Receber notificações de pagamentos USDT / USDC](#receive-payments-with-webhooks) no seu endpoint HTTPS após verificar o suporte a Push na rede selecionada.
* [Validar uma transferência candidata](#verify-deduplicate-and-validate-payments), verificando rede, contrato do token, destinatário e quantidade inteira antes de aplicar sua verificação on-chain e política de confirmação.
* [Recuperar logs de transferências ausentes](#cursor-polling-and-block-range-limits) com consultas limitadas de `eth_getLogs` e um cursor salvo.

## Escolher Webhook, WebSocket ou polling

| Método                                           | Quando usar                                                                                        | Recuperação                                                                                                                              |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [Webhook](https://docs.blockvectra.com/en/guides/webhook-push/)              | Atividade de endereços enviada ao seu receptor HTTPS, incluindo transferências de tokens recebidas | Verifique assinaturas, deduplique IDs de eventos e trate `subscription.gap` / `chain.reorg`; execute replay das correspondências retidas |
| [WebSocket](https://docs.blockvectra.com/en/guides/websocket-subscriptions/) | `logs` filtrados por uma conexão persistente                                                       | Reconecte, refaça as assinaturas e recupere blocos perdidos                                                                              |
| Polling HTTP                                     | Monitoramento agendado ou recuperação de logs históricos com seu próprio cursor                    | Consulte intervalos limitados de `eth_getLogs` e persista o progresso                                                                    |

Consulte `ws` e `subscriptions` na resposta pública de `GET /v1/chains` antes de escolher WebSocket. O suporte a Push exige uma verificação separada: consulte `GET /v1/push/chains` com sua API key. Uma rede sem WebSocket pode usar Webhooks de endereços se estiver listada ali. Use polling quando precisar consultar blocos anteriores ou operar sem conexão persistente.

## Receber pagamentos com Webhooks

### Criar uma assinatura e monitorar o destinatário

[Obtenha uma API key](https://blockvectra.com/en/get-api-key/) e implante um receptor HTTPS na porta 443. Selecione `CHAIN` na lista autenticada de redes Push, defina `RECIPIENT` como seu endereço de depósito e `RECEIVER_URL` como a URL do receptor. Este exemplo de shell exige `jq`; `{}` usa a quantidade padrão de confirmações da rede. Inspecione `min_confirmations`, `default_confirmations` e `max_confirmations` antes de escolher outra quantidade. A [OpenAPI de Push](https://docs.blockvectra.com/openapi/push.yaml) define essas requisições.

```bash
set -eu
umask 077
: "${BLOCKVECTRA_API_KEY:?Set your API key}"
: "${CHAIN:?Select a chain from the Push chain list}"
: "${RECIPIENT:?Set the watched EVM recipient address}"
: "${RECEIVER_URL:?Set your HTTPS receiver URL}"
PUSH_URL='https://api.blockvectra.com/v1/push'

curl --fail-with-body -sS "$PUSH_URL/chains" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" > push-chains.json
jq -e --arg chain "$CHAIN" 'any(.chains[]; .chain == $chain)' push-chains.json
jq -n --arg url "$RECEIVER_URL" --arg chain "$CHAIN" \
  '{url: $url, chains: {($chain): {}}}' > create.json
curl --fail-with-body -sS "$PUSH_URL/subscriptions" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' -d @create.json > subscription.json

SUBSCRIPTION_ID=$(jq -er '.id' subscription.json)
jq -n --arg recipient "$RECIPIENT" '{addresses: [$recipient]}' > addresses.json
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID/addresses/add" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -H 'Content-Type: application/json' -d @addresses.json > address-change.json
curl --fail-with-body -sS "$PUSH_URL/subscriptions/$SUBSCRIPTION_ID" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```

A criação retorna `id` e `secret`. Armazene o segredo com segurança para o receptor; `subscription.json` contém uma credencial. Consulte a assinatura até `applied_version >= change_version` de `address-change.json`, depois registre `chains[CHAIN].applied_from_block`. Novos endereços são associados a partir desse bloco, então continue o polling para qualquer intervalo de pagamentos anterior.

### Verificar, deduplicar e validar pagamentos

Salve a [função de assinatura no corpo bruto](https://docs.blockvectra.com/en/guides/webhook-push/#verify-signatures) como `verify-push.js`. O receptor abaixo aceita um `Request` da Web API em Node.js e lê seus bytes originais antes de interpretar o JSON. Crie `secrets` como um `Map` de strings de IDs de assinatura para segredos armazenados. Defina a configuração confiável `expected` como `{ chain, token, recipient, amountUnits }`: `token` é o contrato verificado da stablecoin naquela rede e `amountUnits` é a quantidade inteira positiva esperada em suas menores unidades. Compare quantidades com `BigInt`, nunca com números de ponto flutuante ou o símbolo do token.

```js
import { verifyPush } from './verify-push.js';

export function selectPayment(data, event, expected) {
  if (data.chain !== expected.chain || event.type !== 'token.transfer' ||
      event.standard !== 'erc20') return null;
  const address = value => typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value);
  if (![event.token, event.to, expected.token, expected.recipient].every(address)) return null;
  if (event.token.toLowerCase() !== expected.token.toLowerCase() ||
      event.to.toLowerCase() !== expected.recipient.toLowerCase()) return null;
  const integer = value => typeof value === 'string' && /^[1-9][0-9]{0,77}$/.test(value);
  if (!integer(event.amount) || !integer(expected.amountUnits)) return null;
  const amount = BigInt(event.amount);
  if (amount >= (1n << 256n) || amount !== BigInt(expected.amountUnits)) return null;
  if (typeof event.id !== 'string' || typeof event.ref !== 'string' ||
      !/^0x[0-9a-f]{64}$/.test(event.tx_hash) ||
      !/^0x[0-9a-f]{64}$/.test(event.block_hash) ||
      !Number.isSafeInteger(event.log_index) || event.log_index < 0 ||
      !Number.isSafeInteger(event.block_number) || event.block_number < 0) return null;
  return {
    eventId: event.id, ref: event.ref, chain: data.chain,
    token: event.token, recipient: event.to, amountUnits: event.amount,
    txHash: event.tx_hash, logIndex: event.log_index,
    blockHash: event.block_hash, blockNumber: event.block_number,
  };
}

export async function receivePayments(request, expected, secrets, store) {
  const rawBody = Buffer.from(await request.arrayBuffer());
  const headers = Object.fromEntries(request.headers);
  if (!verifyPush(rawBody, headers, secrets)) return new Response(null, { status: 401 });
  let message;
  try { message = JSON.parse(rawBody.toString('utf8')); }
  catch { return new Response(null, { status: 400 }); }
  const data = message?.data;
  if (message?.type !== 'push.events' ||
      !Number.isSafeInteger(data?.subscription_id) || data.subscription_id <= 0 ||
      String(data.subscription_id) !== headers['bv-subscription-id'] ||
      data.chain !== expected.chain || !Array.isArray(data.events)) {
    return new Response(null, { status: 400 });
  }
  try {
    await store.transaction(async tx => {
      for (const event of data.events) {
        if (!event || typeof event.id !== 'string') continue;
        const recovery = event.type === 'subscription.gap' || event.type === 'chain.reorg';
        const payment = selectPayment(data, event, expected);
        if (!recovery && !payment) continue;
        if (!await tx.insertEventOnce(data.subscription_id, event)) continue;
        if (recovery) await tx.enqueueRecovery(data.chain, event);
        else await tx.recordPaymentCandidate(payment);
      }
    });
  } catch {
    return new Response(null, { status: 503 });
  }
  return new Response(null, { status: 204 });
}
```

Implemente `store.transaction` com armazenamento durável. Dentro de uma transação, `insertEventOnce` insere um evento com uma chave única `(subscription_id, event.id)` e retorna false para duplicatas; confirme-o junto com `recordPaymentCandidate` ou `enqueueRecovery`. Reverta todas as gravações em caso de falha para que uma nova tentativa possa processar o evento. Tarefas de recuperação também devem ser idempotentes. Retorne 2xx em até 10 segundos apenas após confirmar a transação; imponha o limite de corpo de 1 MiB no seu servidor HTTP.

Este exemplo verifica uma quantidade de pagamento esperada. Para vários pedidos, consulte a configuração confiável de pagamento por rede, token e destinatário e reconcilie pagamentos parciais ou excedentes conforme suas próprias regras. Um candidato ainda precisa de verificação on-chain e da sua política de confirmação antes de ser creditado. Entre assinaturas e polling, reconcilie a mesma transferência por rede, hash de transação e índice do log para que dois caminhos de entrega não a creditem duas vezes; retenha o hash do bloco para acompanhar blocos substituídos.

### Recuperar blocos ausentes ou substituídos

Para `subscription.gap`, enfileire uma consulta de `from_block` até `to_block` usando o caminho de polling abaixo ou conjuntos de dados disponíveis da Data API. `chain.reorg` é um aviso gratuito de que blocos entregues foram substituídos, não uma lacuna de entrega. Marque ou descarte eventos antigos nesse intervalo por `ref`; reconcilie registros de pagamento por `ref` e `tx_hash` com a rede canônica antes de processar eventos canônicos reenviados automaticamente com novos IDs. Deduplique esses eventos por `id`. O aviso de reorg não avança o progresso concluído; registre `complete_through_block` por rede e nunca deduza conclusão pelo maior número de bloco dos eventos.

[Replay](https://docs.blockvectra.com/en/guides/webhook-push/#delivery-retries-and-replay) aceita `chain` e `from_block` dentro do limite atual de `replayable_from_block`. Apenas reenvia correspondências retidas; não consulta períodos anteriores à adição do endereço ou rede, nem períodos em que a assinatura esteve offline. Mantenha um cursor de polling para cobrir esses intervalos e lacunas expiradas. Falhas de requisição e intervalos de replay inválidos estão na [referência de erros](https://docs.blockvectra.com/en/errors/); cobranças de entrega, histórico e endereços-dia são explicadas nas [regras de cobrança](https://docs.blockvectra.com/en/guides/billing-rules/#webhook-push-billing).

As seções restantes implementam filtragem de logs ERC-20 e polling por cursor para monitoramento e recuperação.

## Evento Transfer e parâmetros de filtro

Contratos de tokens ERC-20 padrão emitem o seguinte evento em cada transferência:

```solidity
event Transfer(address indexed from, address indexed to, uint256 value);
```

Ao chamar `eth_getLogs`, envie o endereço do contrato do token e o array `topics` para filtrar logs correspondentes:

| Parâmetro   | Valor                                                                | Descrição                                                                                                                                                                                                                                                                                   |
| ----------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`   | Endereço do contrato do token (ou array de endereços)                | Endereço do contrato da stablecoin desejada. Você pode especificar um endereço (por exemplo, BSC USDT `0x55d398326f99059fF775485246999027B3197955`, Base USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) ou um array de endereços para monitorar vários tokens simultaneamente           |
| `topics[0]` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | Hash da assinatura do evento: `keccak256("Transfer(address,address,uint256)")`                                                                                                                                                                                                              |
| `topics[1]` | `null`                                                               | Endereço do remetente (`from`). Como o monitoramento de depósitos aceita fundos de qualquer carteira de usuário, envie `null` para aceitar qualquer remetente                                                                                                                               |
| `topics[2]` | Endereço do destinatário preenchido com zeros até 32 bytes           | Endereço de destino (`to`). Nas especificações de logs EVM, parâmetros de endereço `indexed` ocupam 32 bytes (64 caracteres hexadecimais). Complete o endereço do destinatário de 20 bytes à esquerda com 12 bytes zero (24 caracteres hexadecimais zero) para formar um topic de 32 bytes. |
| `fromBlock` | Bloco inicial (hexadecimal)                                          | Início do intervalo de blocos da consulta (inclusivo)                                                                                                                                                                                                                                       |
| `toBlock`   | Bloco final (hexadecimal)                                            | Fim do intervalo de blocos da consulta (inclusivo)                                                                                                                                                                                                                                          |

O `value` não indexado (quantidade transferida) é codificado no campo `data` do objeto de log como um `uint256` hexadecimal de 32 bytes. Divida essa quantidade bruta por 10^decimals para obter a quantidade legível do token (por exemplo, 18 casas decimais para BSC USDT; 6 casas decimais para Base e Ethereum USDC).

## Polling por cursor e limites de intervalo de blocos

Um serviço de polling consulta novos blocos em intervalos regulares (por exemplo, a cada 3 a 5 segundos).

### Avanço do cursor

Mantenha um cursor persistente `last_polled_block` (o maior bloco processado e confirmado) no banco de dados:

1. Em cada ciclo de polling, defina `fromBlock = last_polled_block + 1`.
2. Consulte o bloco mais recente da rede com `eth_blockNumber` e calcule a altura de destino segura `safe_head` com base na profundidade de confirmação.
3. Se `fromBlock <= safe_head`, consulte os logs em partes até `safe_head`. Após processar cada parte com sucesso, avance o cursor.

### Limite de intervalo de blocos

O intervalo de blocos de uma chamada `eth_getLogs` é calculado como `toBlock − fromBlock + 1`. Não pode ultrapassar o `max_logs_block_range` publicado para aquela rede em `GET /v1/chains`.

Se uma requisição ultrapassa esse intervalo, o serviço rejeita a chamada com o código de erro `-32602`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "eth_getLogs block range too large: max 1000 blocks",
    "data": {
      "reason": "logs_range_too_large",
      "docs_url": "https://docs.blockvectra.com/en/errors/#logs_range_too_large",
      "retryable": false
    }
  }
}
```

Requisições que ultrapassam o intervalo de blocos retornam o erro JSON-RPC `-32602` (sem cobrança). Na lógica da aplicação, consulte `max_logs_block_range` em `GET /v1/chains` e limite cada parte do polling: `chunk_end = min(fromBlock + max_logs_block_range - 1, safe_head)`.

## Tratar reorganizações de blocos e profundidade de confirmação

Perto do bloco mais recente da blockchain, podem ocorrer reorganizações temporárias de blocos (reorgs). Creditar pagamentos em `latest` sem profundidade de confirmação traz o risco de creditar transações de um ramo órfão que será descartado depois.

Aplique as seguintes medidas para proteger o processamento de pagamentos:

### Profundidade de confirmação

Em vez de consultar até `latest`, consulte até uma altura de bloco de destino segura:

`safe_head = current_head - CONFIRMATION_DEPTH`

Defina `CONFIRMATION_DEPTH` conforme a tolerância a risco da sua aplicação. Consultar apenas até `safe_head` garante que somente blocos com confirmações suficientes sejam processados.

### Reorganizações durante o polling

O EVM JSON-RPC padrão define `removed: true` nos objetos de log apenas nos fluxos de assinatura de logs via WebSocket quando um evento emitido anteriormente é revertido por uma reorg da rede. No polling HTTP com `eth_getLogs`, as consultas retornam logs da rede canônica; logs de blocos reorganizados simplesmente não aparecem em consultas posteriores. Fazer polling dentro de `safe_head` garante que pagamentos sejam processados apenas em blocos com confirmações suficientes.

## Deduplicação por (transactionHash, logIndex)

Monitores de pagamentos devem impor idempotência estrita:

1. **Várias transferências em uma transação**: uma transação pode conter vários eventos `Transfer` para o mesmo endereço de depósito (por exemplo, roteadores de tokens que dividem swaps ou contratos de múltiplos pagamentos). **Importante:** `transactionHash` sozinho não é único por pagamento.
2. **Sobreposição de polling e novas tentativas**: quando serviços de polling reiniciam, recuperam-se de erros temporários de rede ou voltam alguns blocos para tratar reorgs rasas, logs do mesmo intervalo são consultados várias vezes.
3. **Unicidade do índice de log**: `logIndex` identifica a posição relativa do log do evento dentro do bloco. Nas especificações EVM, o identificador único composto canônico de um evento é `(transactionHash, logIndex)`.

Em esquemas de bancos de dados relacionais, declare um índice único composto na tabela de registros de depósitos:

```sql
CREATE UNIQUE INDEX idx_transfers_tx_log ON deposit_records (transaction_hash, log_index);
```

Antes de processar um depósito, verifique as entradas `(transactionHash, logIndex)` existentes para garantir que cada transferência on-chain seja creditada exatamente uma vez.

## Exemplos de código completos

Os exemplos abaixo demonstram como consultar recursos da rede em `/v1/chains`, calcular intervalos seguros de blocos, consultar logs `Transfer` de stablecoins respeitando os limites de intervalo e deduplicar eventos.

**TypeScript**

```ts
import { createPublicClient, formatUnits, http, parseAbiItem } from "viem";

const apiKey = process.env.BLOCKVECTRA_API_KEY;
if (!apiKey) {
  throw new Error("BLOCKVECTRA_API_KEY environment variable is not set");
}

const CHAIN = "bsc_mainnet";
const RPC_URL = "https://api.blockvectra.com/v1/bsc_mainnet";
const CHAINS_URL = "https://api.blockvectra.com/v1/chains";

// Target stablecoin contract address (BSC USDT used in this example)
const TOKEN_CONTRACT = "0x55d398326f99059fF775485246999027B3197955" as const;
const TOKEN_DECIMALS = 18;

// Monitored deposit address
const RECIPIENT_ADDRESS = "0xdded13D555B6DA811103cC1794D3d4330F69632C" as const;

// Confirmation depth to guard against chain reorgs
const CONFIRMATION_DEPTH = 15n;

// 1. Fetch chain capabilities from public metadata endpoint (unauthenticated, unbilled)
const chainsRes = await fetch(CHAINS_URL);
const { chains } = (await chainsRes.json()) as {
  chains: Array<{
    chain: string;
    ws: boolean;
    subscriptions: string[];
    max_logs_block_range: number;
  }>;
};

const chainConfig = chains.find((c) => c.chain === CHAIN);
if (!chainConfig) {
  throw new Error(`Chain ${CHAIN} not found in /v1/chains`);
}

const maxLogsRange = BigInt(chainConfig.max_logs_block_range || 1000);
console.log(`Chain: ${CHAIN} | WebSocket supported: ${chainConfig.ws} | Max logs range: ${maxLogsRange}`);

// 2. Initialize viem client with x-api-key header
const client = createPublicClient({
  transport: http(RPC_URL, {
    fetchOptions: {
      headers: { "x-api-key": apiKey },
    },
  }),
});

// Set to track processed events by composite key: (transactionHash, logIndex)
const processedLogs = new Set<string>();

// 3. Compute query range: subtract confirmation depth from current head
const currentHead = await client.getBlockNumber();
const safeHead = currentHead - CONFIRMATION_DEPTH;

// For demonstration, start cursor 10 blocks before safeHead
let cursor = safeHead > 10n ? safeHead - 10n : 0n;

console.log(`Current head: ${currentHead} | Safe head: ${safeHead} | Polling cursor: ${cursor}`);

while (cursor <= safeHead) {
  const chunkEnd = cursor + maxLogsRange - 1n < safeHead ? cursor + maxLogsRange - 1n : safeHead;

  const logs = await client.getLogs({
    address: TOKEN_CONTRACT,
    event: parseAbiItem(
      "event Transfer(address indexed from, address indexed to, uint256 value)"
    ),
    args: {
      to: RECIPIENT_ADDRESS,
    },
    fromBlock: cursor,
    toBlock: chunkEnd,
  });

  for (const log of logs) {
    const dedupKey = `${log.transactionHash}-${log.logIndex}`;
    if (processedLogs.has(dedupKey)) {
      continue;
    }
    processedLogs.add(dedupKey);

    const tokenAmount = formatUnits(log.args.value ?? 0n, TOKEN_DECIMALS);

    console.log(
      `[Payment Received] Amount: ${tokenAmount} | ` +
      `Tx: ${log.transactionHash} | Log: ${log.logIndex} | Block: ${log.blockNumber}`
    );
  }

  cursor = chunkEnd + 1n;
}

// Run with: npx tsx example.mts
```


  **Python**

```python
from decimal import Decimal
import os
import requests

api_key = os.environ.get("BLOCKVECTRA_API_KEY")
if not api_key:
    raise RuntimeError("BLOCKVECTRA_API_KEY environment variable is not set")

CHAIN = "bsc_mainnet"
RPC_URL = "https://api.blockvectra.com/v1/bsc_mainnet"
CHAINS_URL = "https://api.blockvectra.com/v1/chains"

# Target stablecoin contract address (BSC USDT used in this example)
TOKEN_CONTRACT = "0x55d398326f99059fF775485246999027B3197955"
TOKEN_DECIMALS = 18

# Monitored deposit address
RECIPIENT_ADDRESS = "0xdded13D555B6DA811103cC1794D3d4330F69632C"

# Transfer(address,address,uint256) signature hash
TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

# Left-pad 20-byte address to 32 bytes (64 hex characters)
padded_recipient = f"0x{RECIPIENT_ADDRESS.lower()[2:].rjust(64, '0')}"

# Confirmation depth to guard against chain reorgs
CONFIRMATION_DEPTH = 15

# 1. Fetch chain capabilities from public metadata endpoint (unauthenticated, unbilled)
chains_res = requests.get(CHAINS_URL, timeout=10)
chains_res.raise_for_status()
chain_list = chains_res.json().get("chains", [])

chain_config = next((c for c in chain_list if c["chain"] == CHAIN), None)
if not chain_config:
    raise RuntimeError(f"Chain {CHAIN} not found in /v1/chains")

max_logs_range = chain_config.get("max_logs_block_range", 1000)
ws_supported = chain_config.get("ws", False)
print(f"Chain: {CHAIN} | WebSocket supported: {ws_supported} | Max logs range: {max_logs_range}")

def rpc_request(method: str, params: list):
    res = requests.post(
        RPC_URL,
        headers={
            "Content-Type": "application/json",
            "x-api-key": api_key,
        },
        json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
        timeout=15,
    )
    res.raise_for_status()
    payload = res.json()
    if "error" in payload:
        err = payload["error"]
        raise RuntimeError(f"JSON-RPC error {err.get('code')}: {err.get('message')}")
    return payload["result"]

# 2. Query latest block number and calculate safe head
current_head_hex = rpc_request("eth_blockNumber", [])
current_head = int(current_head_hex, 16)
safe_head = max(0, current_head - CONFIRMATION_DEPTH)

# For demonstration, start cursor 10 blocks before safe_head
cursor = max(0, safe_head - 10)
print(f"Current head: {current_head} | Safe head: {safe_head} | Polling cursor: {cursor}")

# In-memory deduplication set using (transactionHash, logIndex)
processed_logs = set()

while cursor <= safe_head:
    chunk_end = min(cursor + max_logs_range - 1, safe_head)

    logs = rpc_request(
        "eth_getLogs",
        [
            {
                "address": TOKEN_CONTRACT,
                "fromBlock": hex(cursor),
                "toBlock": hex(chunk_end),
                "topics": [
                    TRANSFER_TOPIC0,
                    None,  # match any sender
                    padded_recipient,  # match monitored recipient
                ],
            }
        ],
    )

    for log in logs:
        tx_hash = log["transactionHash"]
        log_index = int(log["logIndex"], 16)
        dedup_key = (tx_hash, log_index)

        if dedup_key in processed_logs:
            continue
        processed_logs.add(dedup_key)

        raw_amount = int(log["data"], 16)
        token_amount = Decimal(raw_amount) / (Decimal(10) ** TOKEN_DECIMALS)
        block_number = int(log["blockNumber"], 16)

        print(
            f"[Payment Received] Amount: {token_amount} | "
            f"Tx: {tx_hash} | Log: {log_index} | Block: {block_number}"
        )

    cursor = chunk_end + 1

# Run with: python example.py
```


## Regras de cobrança e guias relacionados

* Para detalhes sobre medição de requisições, pesos de CU e cobrança por código de erro, consulte [Regras de cobrança: erros e requisições sem cobrança](https://docs.blockvectra.com/en/guides/billing-rules/).
* Para orientações detalhadas sobre limites de intervalo de blocos de `eth_getLogs` e divisão em partes, consulte [Limites de intervalo de blocos do eth\_getLogs e consultas em partes](https://docs.blockvectra.com/en/guides/getlogs-block-range/).
* Para diferenças entre consultas RPC em tempo real ao nó e APIs de transferências históricas indexadas, consulte [Dados recentes da rede e histórico indexado: quando usar eth\_getLogs ou Transfers](https://docs.blockvectra.com/en/guides/logs-vs-transfers/).

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