# Webhooks ve RPC ile USDT / USDC Ödemelerini İzleme

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

Stablecoin ödeme izleme veya borsa yatırma tespiti için, Webhook'lar, WebSocket log'ları veya HTTP polling kullanarak EVM zincirlerinde gelen ERC-20 USDT / USDC transferlerini izleyin. Geliştiriciler ve yapay zeka ajanları (AI Agent) aynı API'leri kullanır; ödemeleri işlemeden önce zinciri, token sözleşmesini, alıcıyı ve onay derinliğini seçin. [USDT / USDC transfer izleme çözümü](https://blockvectra.com/tr/use-cases/stablecoin-payments/) sayfasında yatırma, satıcı bildirimi veya çıkış ödemesi (payout) iş akışlarından birini seçin.

* **İlk adım:** Bir API key ve HTTPS alıcınızla başlayarak [bir abonelik oluşturun ve alıcıyı izleyin](#create-a-subscription-and-watch-the-recipient).
* **Tamamlanma koşulu:** Eşleşen bir transfer imza, zincir, token, alıcı ve tam sayı tutar kontrollerini geçer, ödeme adayı olarak bir kez saklanır ve alıcı HTTP `204` döndürür; bakiyeyi hesaba geçirmeden önce onay politikanız kapsamında zincir üzerinde doğrulayın.

[Stablecoin ödeme iş akışları](https://blockvectra.com/tr/use-cases/stablecoin-payments/).

Temel transfer izleme kullanılabilir. Tutar ve token filtreleme alıcınızda çalışır. Sunucu tarafı koşullar, çok aşamalı onaylar ve IM uyarıları yakında sunulacak.

Geliştiriciler ve Yapay Zeka Ajanları için: Bir [API key](https://console.blockvectra.com/login/?next=%2Fkeys%2F) ve kendi HTTPS alıcınızla başlayın; token sözleşmelerini ve tutarları uygulamanızda filtreleyin. [Webhook kurulumunu kopyalayın](#create-a-subscription-and-watch-the-recipient).

## Bu rehberin tamamlamanıza yardımcı olduğu görevler

* Seçilen zincir için Push desteğini kontrol ettikten sonra HTTPS uç noktanızda [USDT / USDC ödeme bildirimleri alın](#receive-payments-with-webhooks).
* Zincir üstü doğrulama ve onay politikanızı uygulamadan önce zincirini, token sözleşmesini, alıcısını ve tam sayı tutarını kontrol ederek [bir transfer adayını doğrulayın](#verify-deduplicate-and-validate-payments).
* Sınırlı `eth_getLogs` sorguları ve kaydedilmiş bir imleç ile [eksik transfer log'larını geriye dönük doldurun](#cursor-polling-and-block-range-limits).

## Webhook, WebSocket veya polling seçimi

| Yöntem                                           | Ne için kullanılır                                                                    | Kurtarma                                                                                                                                             |
| ------------------------------------------------ | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Webhook](https://docs.blockvectra.com/tr/guides/webhook-push/)              | Gelen token transferleri dahil olmak üzere HTTPS alıcınıza gönderilen adres etkinliği | İmzaları doğrulayın, olay kimliklerini tekilleştirin ve `subscription.gap` / `chain.reorg` durumlarını yönetin; saklanan eşleşmeleri yeniden oynatın |
| [WebSocket](https://docs.blockvectra.com/tr/guides/websocket-subscriptions/) | Kalıcı bir bağlantı üzerinden filtrelenmiş `logs`                                     | Yeniden bağlanın, yeniden abone olun ve kaçırılan blokları geriye dönük doldurun                                                                     |
| HTTP polling                                     | Kendi imlecinizle zamanlanmış izleme veya geçmiş log'ları geriye dönük doldurma       | Sınırlı `eth_getLogs` aralıklarını sorgulayın ve ilerlemeyi kalıcı hale getirin                                                                      |

WebSocket'i seçmeden önce genel `GET /v1/chains` yanıtındaki `ws` ve `subscriptions` alanlarını okuyun. Push desteği ayrı bir kontroldür: API key'inizle `GET /v1/push/chains` okuyun. WebSocket desteği olmayan bir zincir, orada listeleniyorsa adres Webhook'larını kullanabilir. Daha önceki blokları taramanız gerektiğinde veya kalıcı bir bağlantı olmadan çalışırken polling kullanın.

## Webhook'lar ile ödeme alma

### Abonelik oluşturun ve alıcıyı izleyin

[Bir API key alın](https://blockvectra.com/tr/get-api-key/) ve 443 portunda bir HTTPS alıcısı dağıtın. Kimlik doğrulamalı Push zincir listesinden `CHAIN` seçin, `RECIPIENT` değerini yatırma adresinize ve `RECEIVER_URL` değerini alıcı URL'nize ayarlayın. Bu kabuk örneği `jq` gerektirir; `{}` zincirin varsayılan onay sayısını kullanır. Farklı bir sayı seçmeden önce `min_confirmations`, `default_confirmations` ve `max_confirmations` değerlerini inceleyin. [Push OpenAPI](https://docs.blockvectra.com/openapi/push.yaml) bu istekleri tanımlar.

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

Oluşturma işlemi `id` ve `secret` döndürür. Secret değerini alıcı için güvenli bir şekilde saklayın; `subscription.json` bir kimlik bilgisi içerir. `address-change.json` dosyasındaki `applied_version >= change_version` olana kadar aboneliği yoklayın, ardından `chains[CHAIN].applied_from_block` değerini kaydedin. Yeni adresler bu bloktan itibaren eşleşir, bu nedenle daha önceki ödeme aralıkları için yoklamaya devam edin.

### Ödemeleri doğrulayın, tekilleştirin ve geçerleyin

[Ham gövde imza işlevini](https://docs.blockvectra.com/tr/guides/webhook-push/#verify-signatures) `verify-push.js` olarak kaydedin. Aşağıdaki alıcı, Node.js'de bir Web API `Request` kabul eder ve JSON ayrıştırmasından önce orijinal baytlarını okur. `secrets` nesnesini, abonelik kimliği dizelerinden saklanan secret'lara eşleyen bir `Map` olarak oluşturun. Güvenilir `expected` yapılandırmasını `{ chain, token, recipient, amountUnits }` olarak ayarlayın: `token`, o zincirdeki doğrulanmış stablecoin sözleşmesidir ve `amountUnits`, en küçük birimlerindeki beklenen pozitif tam sayı tutarıdır. Tutarları asla kayan noktalı sayılarla veya token sembolüyle değil, `BigInt` ile karşılaştırın.

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

Dayanıklı depolama ile `store.transaction` uygulayın. Tek bir işlem içinde `insertEventOnce`, benzersiz bir `(subscription_id, event.id)` anahtarı altında bir olay ekler ve yinelenenler için false döndürür; bunu `recordPaymentCandidate` veya `enqueueRecovery` ile birlikte commit edin. Bir yeniden denemenin olayı işleyebilmesi için başarısızlık durumunda tüm yazmaları geri alın. Kurtarma işleri de idempotent olmalıdır. Yalnızca commit ettikten sonra 10 saniye içinde 2xx döndürün; HTTP sunucunuzda 1 MiB gövde sınırını uygulayın.

Bu örnek, beklenen tek bir ödeme tutarını kontrol eder. Birden fazla sipariş için, zincir, token ve alıcıya göre güvenilir ödeme yapılandırmasını arayın ve kendi kurallarınız altında kısmi veya fazla ödemeleri uzlaştırın. Bir adayın hesaba geçirilmeden önce hala zincir üstü doğrulamaya ve onay politikanıza ihtiyacı vardır. Abonelikler ve yoklama arasında, iki teslimat yolunun aynı transferi iki kez hesaba geçirmemesi için transferi zincir, işlem karması ve log dizini ile uzlaştırın; değiştirilen blokları izlemek için blok karmasını saklayın.

### Eksik veya değiştirilen blokları kurtarın

`subscription.gap` için, aşağıdaki yoklama yolunu veya mevcut Data API veri kümelerini kullanarak `from_block` ile `to_block` arasını tarayan bir kuyruk işi ekleyin. `chain.reorg`, teslim edilen blokların değiştirildiğine dair ücretsiz bir bildirimdir, bir teslimat boşluğu değildir. Bu aralıktaki eski olayları `ref` ile işaretleyin veya atın; yeni kimliklerle otomatik olarak yeniden teslim edilen kanonik olayları işlemeden önce ödeme kayıtlarını `ref` ve `tx_hash` ile kanonik zincire göre uzlaştırın. Bu olayları `id` ile tekilleştirin. Yeniden düzenleme bildirimi tamamlanan ilerlemeyi ilerletmez; zincir başına `complete_through_block` kaydedin, tamamlanmayı asla en büyük olay blok numarasından çıkarmayın.

[Replay](https://docs.blockvectra.com/tr/guides/webhook-push/#delivery-retries-and-replay), mevcut `replayable_from_block` sınırı içindeki `chain` ve `from_block` parametrelerini kabul eder. Yalnızca saklanan eşleşmeleri yeniden gönderir; adres veya zincir eklenmeden önceki dönemleri veya aboneliğin çevrimdışı olduğu dönemleri taramaz. Bu aralıkları ve süresi dolmuş boşlukları karşılamak için bir yoklama imleci tutun. İstek hataları ve geçersiz yeniden oynatma aralıkları [hata referansı](https://docs.blockvectra.com/tr/errors/) sayfasında ele alınmıştır; teslimat, geçmiş ve adres-gün ücretleri [faturalandırma kuralları](https://docs.blockvectra.com/tr/guides/billing-rules/#webhook-push-billing) sayfasında açıklanmaktadır.

Kalan bölümler, izleme ve kurtarma için ERC-20 log filtreleme ve imleç tabanlı yoklamayı uygular.

## Transfer Olayı ve Filtre Parametreleri

Standart ERC-20 token sözleşmeleri, her transferde aşağıdaki olayı yayar:

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

`eth_getLogs` çağırırken, eşleşen log'ları filtrelemek için token sözleşme adresini ve `topics` dizisini iletin:

| Parametre   | Değer                                                                | Açıklama                                                                                                                                                                                                                                                               |
| ----------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`   | Token sözleşme adresi (veya adres dizisi)                            | Hedef stablecoin sözleşme adresi. Tek bir adres belirtebilir (örneğin BSC USDT `0x55d398326f99059fF775485246999027B3197955`, Base USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) veya birden fazla token'ı aynı anda izlemek için bir adres dizisi iletebilirsiniz |
| `topics[0]` | `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` | Olay imza karması: `keccak256("Transfer(address,address,uint256)")`                                                                                                                                                                                                    |
| `topics[1]` | `null`                                                               | Gönderici adresi (`from`). Yatırma izleme herhangi bir kullanıcı cüzdanından gelen fonları kabul ettiğinden, herhangi bir göndericiyle eşleştirmek için `null` iletin                                                                                                  |
| `topics[2]` | 32 baytlık sıfır dolgulu alıcı adresi                                | Hedef adresi (`to`). EVM log belirtimleri uyarınca `indexed` adres parametreleri 32 bayt (64 onaltılık karakter) yer kaplar. 32 baytlık bir topic oluşturmak için 20 baytlık alıcı adresinin solunu 12 sıfır baytıyla (24 onaltılık sıfır karakteri) doldurun.         |
| `fromBlock` | Başlangıç bloğu (onaltılık)                                          | Sorgu blok aralığının başlangıcı (dahil)                                                                                                                                                                                                                               |
| `toBlock`   | Bitiş bloğu (onaltılık)                                              | Sorgu blok aralığının sonu (dahil)                                                                                                                                                                                                                                     |

İndekslenmemiş `value` (transfer tutarı), log nesnesinin `data` alanında 32 baytlık onaltılık bir `uint256` olarak kodlanır. İnsan tarafından okunabilir token tutarını elde etmek için bu ham tutarı 10^decimals değerine bölün (örneğin BSC USDT için 18 ondalık; Base ve Ethereum USDC için 6 ondalık).

## İmleç Yoklaması ve Blok Aralığı Sınırları

Bir yoklama servisi, düzenli aralıklarla (örneğin her 3 ila 5 saniyede bir) yeni blokları sorgular.

### İmleci İlerletme

Veritabanınızda kalıcı bir imleç olan `last_polled_block` (işlenen ve commit edilen en yüksek blok) saklayın:

1. Her yoklama döngüsü için `fromBlock = last_polled_block + 1` olarak ayarlayın.
2. `eth_blockNumber` aracılığıyla geçerli zincir başlığını sorgulayın ve onay derinliğinize göre güvenli hedef yükseklik olan `safe_head` değerini hesaplayın.
3. `fromBlock <= safe_head` ise, `safe_head` değerine kadar olan parçalar halinde log'ları sorgulayın. Her parçayı başarıyla işledikten sonra imleci ilerletin.

### Blok Aralığı Sınırı

Tek bir `eth_getLogs` çağrısının blok aralığı `toBlock − fromBlock + 1` olarak hesaplanır. Bu değer, `GET /v1/chains` içinde o zincir için yayınlanan `max_logs_block_range` değerini aşmamalıdır.

Bir istek bu aralığı aşarsa, hizmet çağrıyı `-32602` hata koduyla reddeder:

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

Blok aralığını aşan istekler `-32602` JSON-RPC hatası döndürür (faturalandırılmaz). Uygulama mantığınızda `GET /v1/chains` üzerinden `max_logs_block_range` değerini okuyun ve her yoklama dilimini sınırlayın: `chunk_end = min(fromBlock + max_logs_block_range - 1, safe_head)`.

## Blok Yeniden Düzenlemelerini (Reorg) ve Onay Derinliğini Yönetme

Blokzincirin uç noktasına yakın yerlerde geçici blok yeniden düzenlemeleri (reorg) meydana gelebilir. Onay derinliği olmadan ödemeleri `latest` üzerinden hesaba geçirmek, sonradan atılan yetim bir koldaki işlemleri hesaba geçirme riski taşır.

Ödeme işlemeyi korumak için aşağıdaki önlemleri uygulayın:

### Onay Derinliği

`latest` seviyesine kadar sorgulamak yerine güvenli bir hedef blok yüksekliğine kadar sorgulayın:

`safe_head = current_head - CONFIRMATION_DEPTH`

`CONFIRMATION_DEPTH` değerini uygulamanızın risk toleransına göre ayarlayın. Yalnızca `safe_head` seviyesine kadar sorgulama yapmak, yalnızca yeterli onaya sahip blokların işlenmesini sağlar.

### Yoklama Sırasında Yeniden Düzenlemeler

Standart EVM JSON-RPC, yalnızca zincir reorg nedeniyle daha önce yayınlanan bir olay geri alındığında WebSocket logs abonelik akışlarındaki log nesnelerinde `removed: true` ayarlar. HTTP üzerinden `eth_getLogs` ile yoklama yaparken, sorgular kanonik zincirdeki log'ları döndürür; yeniden düzenlenen log'lar sonraki sorgularda görünmez. `safe_head` içinde yoklama yapmak, ödemelerin yalnızca yeterince onaylanmış bloklarda işlenmesini sağlar.

## (transactionHash, logIndex) ile Tekilleştirme

Ödeme dinleyicileri katı bir idempotentlik (idempotency) uygulamalıdır:

1. **Tek Bir İşlemde Birden Fazla Transfer**: Tek bir işlem, aynı yatırma adresine birden fazla `Transfer` olayı içerebilir (örneğin takasları bölen token yönlendiricileri veya çoklu ödeme sözleşmeleri). **Önemli:** `transactionHash` tek başına ödeme başına benzersiz değildir.
2. **Çakışan Yoklama ve Yeniden Denemeler**: Yoklama servisleri yeniden başladığında, geçici ağ hatalarından kurtulduğunda veya sığ reorg'ları işlemek için birkaç blok geri sarıldığında, aynı blok aralığındaki log'lar birden çok kez sorgulanır.
3. **Log İndeksi Benzersizliği**: `logIndex`, olay log'unun blok içindeki göreceli konumunu tanımlar. EVM belirtimleri uyarınca, bir olay için kanonik bileşik benzersiz tanımlayıcı `(transactionHash, logIndex)` şeklindedir.

İlişkisel veritabanı şemalarında, yatırma kayıtları tablonuzda bileşik bir benzersiz indeks bildirin:

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

Bir yatırma işlemini işlemeden önce, her bir zincir üstü transferin tam olarak bir kez hesaba geçmesini garanti etmek için mevcut `(transactionHash, logIndex)` kayıtlarını kontrol edin.

## Eksiksiz Kod Örnekleri

Aşağıdaki örnekler, `/v1/chains` üzerinden ağ yeteneklerinin alınmasını, güvenli blok aralıklarının hesaplanmasını, aralık limitlerine uygun olarak stablecoin `Transfer` log'larının yoklanmasını ve olayların tekilleştirilmesini göstermektedir.

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


## Faturalandırma Kuralları ve İlgili Rehberler

* İstek ölçümü, CU ağırlıkları ve hata kodu faturalandırma belirlemeleri hakkında ayrıntılar için [Faturalandırma Kuralları: Hatalar ve Faturalandırılmayan İstekler](https://docs.blockvectra.com/tr/guides/billing-rules/) sayfasına bakın.
* `eth_getLogs` blok aralığı limitleri ve parçalama mantığı hakkında ayrıntılı rehberlik için [eth\_getLogs Blok Aralığı Limitleri ve Parçalı Sorgular](https://docs.blockvectra.com/tr/guides/getlogs-block-range/) sayfasına bakın.
* Gerçek zamanlı RPC düğüm sorguları ile indekslenmiş geçmiş transfer API'leri arasındaki farklar için [Zincir Başı ile İndekslenmiş Geçmiş: eth\_getLogs ile Transfers Ne Zaman Kullanılır](https://docs.blockvectra.com/tr/guides/logs-vs-transfers/) sayfasına bakın.

## Sonraki adımlar

* BlockVectra'nın indekslediği her veri kümesini görmek için [veri kümeleri dizinine göz atın](https://blockvectra.com/tr/data/).
* Hesabınızın neleri içerdiğini kontrol etmek için [ücretsiz planı ve fiyatlandırmayı inceleyin](https://blockvectra.com/tr/pricing/#free).
* Bir API key oluşturmak için [konsolda oturum açın](https://console.blockvectra.com/login/?next=%2Fkeys%2F).
