Skip to content

Backfill historical events

Scan archive logs with deterministic, resumable block ranges.

Build a transfer history, audit trail, or analytics table from months of contract events, and resume it after any failure without starting over. This guide scans USDC Transfer logs in bounded block ranges and stores a checkpoint after each range. Older ranges need archive access; check Supported networks for coverage on your network.

Prerequisites

  • A GraphOps RPC API key.
  • Archive access for the target chain.
  • A durable store for logs and range progress.
  • An idempotency key such as (blockHash, transactionHash, logIndex).

How the backfill runs

Ranges 1 and 2 are stored and the checkpoint records block 18,003,839. Range 3 is in flight; after its logs are written, the checkpoint moves to 18,005,759. A restart resumes from the checkpoint and repeats at most one range.
  1. Choose a starting block and a range size. This guide uses 1,920 blocks per range.
  2. Request one range with eth_getLogs, filtered by contract address and event topic.
  3. Write the returned logs idempotently, keyed by (blockHash, transactionHash, logIndex).
  4. Store the range's last block as your checkpoint, then request the next range.

Run one live consumer separately for new blocks, such as a WebSocket subscription, and stop the backfill where the live consumer starts.

Request

Each variant requests the first range, blocks 18,000,000 to 18,001,919. Repeat the same request shape for each following range.

curl "https://rpc.graphops.xyz/v1/evm/1" \
  -H "Authorization: Bearer <GRAPHOPS_API_KEY>" \
  -H "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_getLogs",
    "params": [
      {
        "fromBlock": "0x112a880",
        "toBlock": "0x112afff",
        "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
        ]
      }
    ]
  }'

Range sizing

Start with small ranges and widen them only after observing response size and latency. Avoid unbounded fromBlock to latest scans; a failed broad request repeats all of its work.

SituationAction
TimeoutReduce range size.
Response too largeReduce range size or add more selective topics.
Sparse logsIncrease range size gradually.
Rate limit (429)Lower concurrency and add backoff with jitter.

Progress tracking

Store the checkpoint in the same transaction as the range's logs, so a restart never skips a range. After the first range, the record looks like this:

{
  "network": "ethereum",
  "chainId": 1,
  "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "topic0": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
  "lastCompletedBlock": 18001919
}

On restart, read lastCompletedBlock and request the next range from the block after it.

Next steps