# So überwachen Sie USDT- / USDC-Zahlungen mit Webhooks und RPC

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

Für die Überwachung von Stablecoin-Zahlungen oder die Erkennung von Börseneinzahlungen überwachen Sie eingehende ERC-20 USDT- / USDC-Transfers auf EVM-Chains mithilfe von Webhooks, WebSocket-Logs oder HTTP-Polling. Entwickler und KI-Agenten nutzen dieselben APIs; wählen Sie vor der Zahlungsverarbeitung die Chain, den Token-Vertrag, den Empfänger und die Bestätigungstiefe aus. Wählen Sie einen Einzahlungs-, Händlerbenachrichtigungs- oder Auszahlungs-Workflow in der [USDT- / USDC-Transferüberwachungslösung](https://blockvectra.com/de/use-cases/stablecoin-payments/).

* **Erster Schritt:** [Abonnement erstellen und Empfänger überwachen](#create-a-subscription-and-watch-the-recipient), beginnend mit einem API-Schlüssel und Ihrem HTTPS-Empfänger.
* **Abgeschlossen, wenn:** Ein passender Transfer die Prüfungen von Signatur, Chain, Token, Empfänger und ganzzahligem Betrag besteht, einmalig als Zahlungskandidat gespeichert wird und der Empfänger HTTP `204` zurückgibt; verifizieren Sie ihn on-chain gemäß Ihrer Bestätigungsrichtlinie, bevor Sie ihn gutschreiben.

[Workflows für Stablecoin-Zahlungen](https://blockvectra.com/de/use-cases/stablecoin-payments/).

Grundlegende Transferüberwachung ist verfügbar. Die Filterung von Beträgen und Token erfolgt in Ihrem Empfänger. Serverseitige Bedingungen, mehrere Bestätigungsstufen und IM-Benachrichtigungen folgen in Kürze.

Für Entwickler und KI-Agenten: Starten Sie mit einem [API-Schlüssel](https://console.blockvectra.com/login/?next=%2Fkeys%2F) und Ihrem eigenen HTTPS-Empfänger; filtern Sie Token-Verträge und Beträge in Ihrer Anwendung. [Webhook-Einrichtung kopieren](#create-a-subscription-and-watch-the-recipient).

## Aufgaben, die dieser Leitfaden abdeckt

* [USDT- / USDC-Zahlungsbenachrichtigungen empfangen](#receive-payments-with-webhooks) an Ihrem HTTPS-Endpunkt nach Prüfung der Push-Unterstützung für die ausgewählte Chain.
* [Einen Transferkandidaten validieren](#verify-deduplicate-and-validate-payments), indem Chain, Token-Vertrag, Empfänger und ganzzahliger Betrag geprüft werden, bevor Sie Ihre On-Chain-Verifizierung und Bestätigungsrichtlinie anwenden.
* [Fehlende Transfer-Logs nacherfassen](#cursor-polling-and-block-range-limits) mit begrenzten `eth_getLogs`-Abfragen und einem gespeicherten Cursor.

## Webhook, WebSocket oder Polling wählen

| Methode                                          | Verwendung                                                                                     | Wiederherstellung                                                                                                                           |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| [Webhook](https://docs.blockvectra.com/de/guides/webhook-push/)              | An Ihren HTTPS-Empfänger gesendete Adressaktivität, einschließlich eingehender Token-Transfers | Signaturen verifizieren, Ereignis-IDs deduplizieren und `subscription.gap` / `chain.reorg` behandeln; gespeicherte Treffer erneut abspielen |
| [WebSocket](https://docs.blockvectra.com/de/guides/websocket-subscriptions/) | Gefilterte `logs` über eine persistente Verbindung                                             | Erneut verbinden, neu abonnieren und verpasste Blöcke nacherfassen                                                                          |
| HTTP-Polling                                     | Geplante Überwachung oder historische Log-Nacherfassung mit eigenem Cursor                     | Begrenzte `eth_getLogs`-Bereiche abfragen und Fortschritt dauerhaft speichern                                                               |

Prüfen Sie `ws` und `subscriptions` in der öffentlichen Antwort von `GET /v1/chains`, bevor Sie sich für WebSocket entscheiden. Die Push-Unterstützung ist eine separate Prüfung: Rufen Sie `GET /v1/push/chains` mit Ihrem API-Schlüssel ab. Eine Chain ohne WebSocket kann Adress-Webhooks nutzen, sofern sie dort aufgeführt ist. Verwenden Sie Polling, wenn Sie frühere Blöcke durchsuchen müssen oder ohne persistente Verbindung arbeiten möchten.

## Zahlungen mit Webhooks empfangen

### Abonnement erstellen und Empfänger überwachen

[API-Schlüssel anfordern](https://blockvectra.com/de/get-api-key/) und einen HTTPS-Empfänger auf Port 443 bereitstellen. Wählen Sie `CHAIN` aus der authentifizierten Push-Chain-Liste, setzen Sie `RECIPIENT` auf Ihre Einzahlungsadresse und `RECEIVER_URL` auf Ihre Empfänger-URL. Dieses Shell-Beispiel setzt `jq` voraus; `{}` verwendet die standardmäßige Bestätigungsanzahl der Chain. Prüfen Sie `min_confirmations`, `default_confirmations` und `max_confirmations`, bevor Sie eine abweichende Anzahl wählen. Die [Push-OpenAPI](https://docs.blockvectra.com/openapi/push.yaml) definiert diese Anfragen.

```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"
```

Die Erstellung gibt `id` und `secret` zurück. Bewahren Sie das Secret sicher für den Empfänger auf; `subscription.json` enthält Zugangsdaten. Rufen Sie das Abonnement wiederholt ab, bis `applied_version >= change_version` aus `address-change.json` erreicht ist, und notieren Sie anschließend `chains[CHAIN].applied_from_block`. Neue Adressen erfassen Übereinstimmungen erst ab diesem Block; führen Sie daher für frühere Zahlungsintervalle weiterhin Polling durch.

### Zahlungen verifizieren, deduplizieren und validieren

Speichern Sie die [Signaturfunktion für den Raw-Body](https://docs.blockvectra.com/de/guides/webhook-push/#verify-signatures) als `verify-push.js`. Der folgende Empfänger nimmt einen Web-API-`Request` in Node.js entgegen und liest dessen ursprüngliche Bytes vor dem JSON-Parsing. Erstellen Sie `secrets` als eine `Map`, die Abonnement-ID-Strings den gespeicherten Secrets zuordnet. Setzen Sie die vertrauenswürdige `expected`-Konfiguration auf `{ chain, token, recipient, amountUnits }`: `token` ist der verifizierte Stablecoin-Vertrag auf dieser Chain und `amountUnits` ist der erwartete positive ganzzahlige Betrag in seinen kleinsten Einheiten. Vergleichen Sie Beträge immer mit `BigInt`, niemals mit Gleitkommazahlen oder dem Token-Symbol.

```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 });
}
```

Implementieren Sie `store.transaction` mit persistentem Speicher. Innerhalb einer Transaktion fügt `insertEventOnce` ein Ereignis unter einem eindeutigen Schlüssel `(subscription_id, event.id)` ein und gibt bei einem Duplikat false zurück; committen Sie dies gemeinsam mit `recordPaymentCandidate` oder `enqueueRecovery`. Rollen Sie bei Fehlern alle Schreibvorgänge zurück, damit ein erneuter Versuch das Ereignis verarbeiten kann. Wiederherstellungsjobs müssen ebenfalls idempotent sein. Geben Sie den HTTP-Status 2xx innerhalb von 10 Sekunden erst nach erfolgreichem Commit zurück; erzwingen Sie das Body-Limit von 1 MiB in Ihrem HTTP-Server.

Dieses Beispiel prüft einen einzelnen erwarteten Zahlungsbetrag. Bei mehreren Bestellungen ermitteln Sie die vertrauenswürdige Zahlungskonfiguration anhand von Chain, Token und Empfänger und gleichen Teil- oder Überzahlungen nach Ihren eigenen Regeln ab. Ein Kandidat erfordert vor der Gutschrift weiterhin eine On-Chain-Verifizierung und die Anwendung Ihrer Bestätigungsrichtlinie. Gleichen Sie über Abonnements und Polling hinweg denselben Transfer anhand von Chain, Transaktions-Hash und Log-Index ab, damit zwei Übertragungswege denselben Betrag nicht doppelt gutschreiben; behalten Sie den Block-Hash bei, um ersetzte Blöcke nachzuverfolgen.

### Fehlende oder ersetzte Blöcke wiederherstellen

Reihen Sie bei `subscription.gap` einen Suchlauf von `from_block` bis `to_block` über den unten beschriebenen Polling-Pfad oder verfügbare Data API-Datensätze in eine Warteschlange ein. `chain.reorg` ist eine kostenlose Benachrichtigung darüber, dass zugestellte Blöcke ersetzt wurden, und keine Zustellungslücke. Markieren oder verwerfen Sie alte Ereignisse in diesem Bereich anhand von `ref`; gleichen Sie Zahlungsdatensätze anhand von `ref` und `tx_hash` mit der kanonischen Chain ab, bevor Sie automatisch neu zugestellte kanonische Ereignisse mit neuen IDs verarbeiten. Deduplizieren Sie diese Ereignisse anhand von `id`. Die Reorg-Benachrichtigung rückt den abgeschlossenen Fortschritt nicht vor; protokollieren Sie `complete_through_block` pro Chain und schließen Sie niemals aus der höchsten Ereignis-Blocknummer auf den Abschluss.

[Replay](https://docs.blockvectra.com/de/guides/webhook-push/#delivery-retries-and-replay) akzeptiert `chain` und `from_block` innerhalb der aktuellen `replayable_from_block`-Grenze. Es sendet nur gespeicherte Treffer erneut; es durchsucht keine Zeiträume vor dem Hinzufügen der Adresse oder Chain oder Zeiträume, in denen das Abonnement offline war. Führen Sie einen Polling-Cursor, um diese Intervalle und abgelaufene Lücken abzudecken. Anforderungsfehler und ungültige Replay-Bereiche werden in der [Fehlerreferenz](https://docs.blockvectra.com/de/errors/) behandelt; Gebühren für Zustellung, Verlauf und Adresstage werden in den [Abrechnungsregeln](https://docs.blockvectra.com/de/guides/billing-rules/#webhook-push-billing) erläutert.

Die folgenden Abschnitte implementieren die ERC-20-Log-Filterung und das cursorbasierte Polling für Überwachung und Wiederherstellung.

## Transfer-Event und Filterparameter

Standardmäßige ERC-20-Token-Verträge emittieren bei jedem Transfer folgendes Event:

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

Übergeben Sie beim Aufruf von `eth_getLogs` die Adresse des Token-Vertrags und das `topics`-Array, um passende Logs zu filtern:

| Parameter   | Wert                                                                 | Beschreibung                                                                                                                                                                                                                                                                 |
| ----------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`   | Adresse des Token-Vertrags (oder Array von Adressen)                 | Zieladresse des Stablecoin-Vertrags. Sie können eine einzelne Adresse angeben (z. B. BSC USDT `0x55d398326f99059fF775485246999027B3197955`, Base USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) oder ein Array von Adressen, um mehrere Token gleichzeitig zu überwachen |
| `topics[0]` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | Event-Signatur-Hash: `keccak256("Transfer(address,address,uint256)")`                                                                                                                                                                                                        |
| `topics[1]` | `null`                                                               | Absenderadresse (`from`). Da die Einzahlungsüberwachung Gelder von jedem Benutzer-Wallet akzeptiert, übergeben Sie `null`, um jeden Absender zu erfassen                                                                                                                     |
| `topics[2]` | Mit Nullen auf 32 Bytes aufgefüllte Empfängeradresse                 | Zieladresse (`to`). Gemäß EVM-Log-Spezifikationen belegen `indexed` Adressparameter 32 Bytes (64 Hex-Zeichen). Füllen Sie die 20-Byte-Empfängeradresse links mit 12 Null-Bytes (24 hexadezimalen Null-Zeichen) auf, um ein 32-Byte-Topic zu bilden.                          |
| `fromBlock` | Startblock (hexadezimal)                                             | Beginn des Abfrage-Blockbereichs (einschließlich)                                                                                                                                                                                                                            |
| `toBlock`   | Endblock (hexadezimal)                                               | Ende des Abfrage-Blockbereichs (einschließlich)                                                                                                                                                                                                                              |

Der nicht-indizierte `value` (Transferbetrag) ist im `data`-Feld des Log-Objekts als 32-Byte hexadezimaler `uint256` codiert. Teilen Sie diesen Rohbetrag durch 10^decimals, um den menschenlesbaren Token-Betrag zu erhalten (z. B. 18 Dezimalstellen für BSC USDT; 6 Dezimalstellen für Base und Ethereum USDC).

## Cursor-Polling und Blockbereichs-Limits

Ein Polling-Dienst fragt neue Blöcke in regelmäßigen Abständen ab (z. B. alle 3 bis 5 Sekunden).

### Weiterschalten des Cursors

Führen Sie einen persistenten Cursor `last_polled_block` (den höchsten verarbeiteten und committeten Block) in Ihrer Datenbank:

1. Setzen Sie für jeden Polling-Zyklus `fromBlock = last_polled_block + 1`.
2. Fragen Sie die aktuelle Chain-Spitze über `eth_blockNumber` ab und berechnen Sie die sichere Zielhöhe `safe_head` basierend auf Ihrer Bestätigungstiefe.
3. Wenn `fromBlock <= safe_head`, fragen Sie Logs in Abschnitten (Chunks) bis zu `safe_head` ab. Nach erfolgreicher Verarbeitung jedes Chunks rücken Sie den Cursor vor.

### Blockbereichs-Limit

Die Blockspanne eines einzelnen `eth_getLogs`-Aufrufs berechnet sich als `toBlock − fromBlock + 1`. Sie darf den für diese Chain in `GET /v1/chains` veröffentlichten Wert `max_logs_block_range` nicht überschreiten.

Wenn eine Anfrage diesen Bereich überschreitet, weist der Dienst den Aufruf mit dem Fehlercode `-32602` ab:

```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
    }
  }
}
```

Anfragen, die den Blockbereich überschreiten, geben den JSON-RPC-Fehler `-32602` zurück (werden nicht berechnet). Lesen Sie in Ihrer Anwendungslogik `max_logs_block_range` aus `GET /v1/chains` aus und begrenzen Sie jeden Polling-Abschnitt: `chunk_end = min(fromBlock + max_logs_block_range - 1, safe_head)`.

## Umgang mit Block-Reorganisationen und Bestätigungstiefe

Nahe der Spitze der Blockchain können temporäre Block-Reorganisationen (Reorgs) auftreten. Werden Zahlungen bei `latest` ohne Bestätigungstiefe gutgeschrieben, besteht das Risiko, Transaktionen auf einem verwaisten Zweig gutzuschreiben, der anschließend verworfen wird.

Wenden Sie folgende Schutzmaßnahmen an, um die Zahlungsverarbeitung abzusichern:

### Bestätigungstiefe

Fragen Sie statt bis `latest` bis zu einer sicheren Zielblockhöhe ab:

`safe_head = current_head - CONFIRMATION_DEPTH`

Legen Sie `CONFIRMATION_DEPTH` entsprechend der Risikotoleranz Ihrer Anwendung fest. Die Abfrage ausschließlich bis `safe_head` stellt sicher, dass nur Blöcke mit ausreichenden Bestätigungen verarbeitet werden.

### Reorganisationen während des Pollings

Standardmäßiges EVM-JSON-RPC setzt `removed: true` bei Log-Objekten nur in WebSocket-Logs-Abonnement-Streams, wenn ein zuvor emittiertes Event aufgrund eines Chain-Reorgs zurückgesetzt wird. Beim Polling über HTTP mit `eth_getLogs` geben Abfragen Logs aus der kanonischen Chain zurück; reorganisierte Logs tauchen in nachfolgenden Abfragen schlichtweg nicht auf. Polling innerhalb von `safe_head` stellt sicher, dass Zahlungen nur auf ausreichend bestätigten Blöcken verarbeitet werden.

## Deduplizierung nach (transactionHash, logIndex)

Zahlungs-Listener müssen strikte Idempotenz erzwingen:

1. **Mehrere Transfers in einer Transaktion**: Eine einzelne Transaktion kann mehrere `Transfer`-Events an dieselbe Einzahlungsadresse enthalten (z. B. Token-Router, die Swaps aufteilen, oder Multi-Auszahlungsverträge). **Wichtig:** `transactionHash` allein ist nicht eindeutig pro Zahlung.
2. **Überlappendes Polling und Wiederholungsversuche**: Wenn Polling-Dienste neu starten, sich von vorübergehenden Netzwerkfehlern erholen oder einige Blöcke zurückspringen, um flache Reorgs zu verarbeiten, werden Logs aus demselben Blockbereich mehrfach abgefragt.
3. **Eindeutigkeit des Log-Index**: Der `logIndex` identifiziert die relative Position des Event-Logs innerhalb des Blocks. Gemäß EVM-Spezifikationen ist der kanonische zusammengesetzte eindeutige Bezeichner für ein Ereignis `(transactionHash, logIndex)`.

Deklarieren Sie in relationalen Datenbankschemata einen zusammengesetzten eindeutigen Index auf Ihrer Tabelle für Einzahlungsdatensätze:

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

Prüfen Sie vor der Verarbeitung einer Einzahlung vorhandene Einträge für `(transactionHash, logIndex)`, um zu gewährleisten, dass jeder On-Chain-Transfer exakt einmal gutgeschrieben wird.

## Vollständige Codebeispiele

Die folgenden Beispiele veranschaulichen das Abrufen von Netzwerkfunktionen aus `/v1/chains`, das Berechnen sicherer Blockbereiche, das Polling von Stablecoin-`Transfer`-Logs unter Einhaltung von Bereichslimits und die Deduplizierung von Ereignissen.

**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
```


## Abrechnungsregeln und verwandte Leitfäden

* Details zur Erfassung von Anfragen, CU-Gewichtungen und der Abrechnung von Fehlercodes finden Sie unter [Abrechnungsregeln: Fehler und nicht berechnete Anfragen](https://docs.blockvectra.com/de/guides/billing-rules/).
* Ausführliche Hinweise zu den Blockbereichslimits von `eth_getLogs` und zur Chunking-Logik finden Sie unter [eth\_getLogs-Blockbereichslimits und gestückelte Abfragen](https://docs.blockvectra.com/de/guides/getlogs-block-range/).
* Zu den Unterschieden zwischen Echtzeit-RPC-Node-Abfragen und indexierten APIs für historische Transfers siehe [Head-of-Chain vs. indexierte Historie: Wann eth\_getLogs und wann Transfers verwendet werden sollten](https://docs.blockvectra.com/de/guides/logs-vs-transfers/).

## Nächste Schritte

* [Datensatzverzeichnis durchsuchen](https://blockvectra.com/de/data/), um alle von BlockVectra indexierten Datensätze zu sehen.
* [Kostenlosen Plan und Preise ansehen](https://blockvectra.com/de/pricing/#free), um den Leistungsumfang Ihres Kontos zu prüfen.
* [In der Konsole anmelden](https://console.blockvectra.com/login/?next=%2Fkeys%2F), um einen API-Schlüssel zu erstellen.
