Prompt

How do I set up an event indexing API for tracking smart contract events with reliable historical backfill?

Latest observation

Jul 22, 2026 · OpenAI APIWeb search: off

To set up an event indexing API for smart contract events with reliable historical backfill, design it as a pipeline with four layers:

  1. Chain data ingestion
  2. Event decoding and normalization
  3. Persistent indexing/storage
  4. API/query layer

The key to reliability is to treat historical backfill and live sync as the same pipeline, just with different starting blocks and retry logic.


1) Define your indexing target

First decide:

  • Which chain(s): Ethereum, L2s, EVM-compatible, etc.
  • Which contracts: one or many, static or user-submitted
  • Which events: specific event signatures or all logs
  • How much history: from deployment block, a given height, or genesis
  • Freshness needs: near-real-time vs delayed

For smart contract events, you usually index:

  • eth_getLogs for historical and live logs
  • optionally eth_subscribe / websocket log subscriptions for live updates

2) Use logs as your source of truth

For EVM chains, contract events are emitted as logs.

Historical backfill

Use eth_getLogs in block ranges:

  • Query by:
    • address
    • topics
    • fromBlock
    • toBlock

Live indexing

Use one of:

  • WebSocket subscriptions for new logs
  • Polling latest blocks and querying logs per block range

Recommendation: even with WebSockets, still run periodic block-range reconciliation to catch missed events.


3) Build a robust backfill strategy

Historical backfill is where most indexers fail. The safest pattern is:

A. Chunk by block ranges

Do not query huge spans at once. Split into manageable ranges:

  • e.g. 1,000–10,000 blocks per request depending on chain/provider limits
  • adapt chunk size based on response size and latency

B. Persist progress checkpoints

Store:

  • last_processed_block
  • last_finalized_block or confirmation depth
  • per-contract / per-task cursor

This lets you resume after failures.

C. Make processing idempotent

Assume the same log may be seen multiple times. Deduplicate using:

  • chain_id
  • block_number
  • transaction_hash
  • log_index

Create a unique constraint on these fields.

D. Reorg handling

For live indexing, only consider blocks final after a confirmation window, e.g.:

  • Ethereum mainnet: 12–64 confirmations depending on risk tolerance
  • L2s: follow chain-specific finality rules

Also support reprocessing recent blocks if a reorg is detected.


4) Event decoding

Logs contain:

  • topics
  • data
  • address
  • metadata like block and tx info

To decode events:

  1. Load the contract ABI
  2. Match topic0 against event signature hash
  3. Decode indexed and non-indexed parameters

Store both:

  • raw log data
  • decoded event fields

That gives you flexibility if ABI decoding changes later.


5) Storage schema

A practical schema:

contracts

  • chain_id
  • address
  • abi_version
  • deployment_block

indexed_events

  • chain_id
  • address
  • block_number
  • block_hash
  • transaction_hash
  • log_index
  • event_signature
  • decoded_json
  • raw_topics
  • raw_data
  • timestamp
  • removed or reorged flag

Indexes to add:

  • (chain_id, address, block_number)
  • (chain_id, event_signature)
  • unique (chain_id, transaction_hash, log_index)

If query volume is high, use:

  • PostgreSQL for metadata and moderate scale
  • ClickHouse/BigQuery/Elastic for analytics-heavy workloads

6) API design

Expose endpoints like:

Get events for a contract

GET /v1/events?chain_id=1&address=0x...&from_block=19000000&to_block=19010000

Filter by event type

GET /v1/events?chain_id=1&event=Transfer

Get decoded event by tx hash

GET /v1/events/{transaction_hash}

Backfill status

GET /v1/indexers/{id}/status

Return:

  • current cursor
  • synced height
  • lag
  • last error
  • reorg status

7) Recommended ingestion architecture

A reliable pattern:

Option A: Worker + queue

  • Scheduler enqueues block ranges
  • Fetcher workers call eth_getLogs
  • Decoder workers normalize logs
  • Writer stores in DB
  • Checkpoint updater advances cursor only after commit

This is the most fault-tolerant approach.

Option B: Single service

Good for small scale, but less resilient.


8) Backfill algorithm

A simple reliable loop:

  1. Determine start_block from deployment or checkpoint
  2. Determine end_block from latest safe/finalized block
  3. Query logs in a range
  4. Decode and write logs transactionally
  5. Update checkpoint only after success
  6. Repeat with next range
  7. Retry on transient errors with exponential backoff

Pseudo-flow:

while cursor <= latest_finalized:
    range = [cursor, min(cursor + chunk_size - 1, latest_finalized)]
    logs = getLogs(range)
    write logs idempotently
    checkpoint = range.end + 1

If a request fails:

  • retry
  • reduce chunk size if provider limits are hit
  • persist failure state for observability

9) Live sync pattern

For live events:

  1. Poll or subscribe for latest blocks
  2. Wait for confirmations
  3. Index confirmed range
  4. Periodically reconcile recent blocks
  5. Detect reorgs by block hash mismatch

A good rule:

  • index latest_finalized, not latest, unless your app can tolerate reorgs

10) Provider considerations

RPC providers often impose:

  • log range limits
  • response size limits
  • rate limits
  • unstable websocket connections

To improve reliability:

  • use multiple RPC providers with failover
  • retry on 429/5xx
  • dynamically shrink block range if response is too large
  • prefer archived nodes for deep history
  • cache contract ABIs locally

11) Reorg-safe data model

To support reorgs:

  • store block_hash
  • mark removed logs if provider emits them
  • when a canonical block changes, delete or invalidate affected rows
  • reprocess affected block ranges

You may maintain:

  • canonical_blocks
  • events
  • reorg_events

12) Operational best practices

Add:

  • metrics
    • indexing lag
    • logs processed/sec
    • error rate
    • retry count
  • alerts
    • stuck cursor
    • growing lag
    • RPC failures
  • dead-letter queue
    • for unprocessable logs
  • audit trail
    • block range processing history

13) Minimal implementation stack

A practical stack:

  • Node.js / TypeScript or Python
  • ethers.js or web3.py
  • PostgreSQL
  • Redis for queue/cursors
  • Worker process for backfill/live sync
  • FastAPI / Express / NestJS for API

14) Example workflow

Backfill

  • Start from contract deployment block
  • Query 2,000 blocks at a time
  • Decode events
  • Insert with unique key on (tx_hash, log_index)
  • Update cursor
  • Continue until caught up

Live

  • Subscribe to new logs or poll every block
  • Wait 12 confirmations
  • Index final blocks
  • Reconcile every N minutes

15) Common pitfalls to avoid

  • Querying too many blocks per RPC call
  • Not storing raw logs
  • Advancing checkpoints before writes succeed
  • Ignoring reorgs
  • Trusting websocket subscriptions alone
  • Not using idempotent inserts
  • Not versioning ABIs
  • Missing archived node support for old history

16) If you want the simplest reliable architecture

Use:

  • eth_getLogs for both backfill and catch-up
  • block-range chunking
  • checkpointing in DB
  • idempotent inserts
  • confirmation delays
  • periodic reconciliation

That gives you a robust indexer without needing overly complex infra.


If you want, I can also provide:

  1. a reference architecture diagram
  2. a Postgres schema
  3. a TypeScript/ethers.js implementation
  4. a Python/web3.py implementation
  5. a FastAPI/Express API example

Observation history

Unlock observation history

Create a free Obsurfable account to unlock historical responses, model comparisons, and deeper brand intelligence.

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, and Meta AI.