sieve

self-hosted indexer for ethereum and the op-stack.
connects straight to p2p. no rpc provider. no bills.

ethereum · base · op mainnet · unichain · world chain

sieve ~/my-indexer
$ sieve --explain
Ethereum and OP-stack event indexer. Connects directly to P2P.
No RPC provider. No API keys. No rate limits. No bills.
$ sieve add-contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --name USDC
✓ Fetching ABI from Etherscan...
✓ Proxy detected → implementation FiatTokenV2_2
✓ Added USDC to sieve.toml
17 events, start_block=6082465
$ sieve
 
sieve 0.3.0
 
config sieve.toml
database postgres://postgres:***@localhost:5432/sieve
tables usdc_transfers
api http://localhost:4000/graphql
 
✓ synced 98,417 blocks in 1m 34s (1,046 blk/s, 12,933 events)
following | block: 24,663,894 | 3s ago | peers: 50
sieve diff --others
# what changes when you drop the RPC provider
Data sourceRPC provider ($225-900/mo)P2P network, plus Base snapshot archives (optional)
SpeedProvider rate limit~1000 blocks/sec (mainnet)
ChainsWhatever the provider sellsEthereum + OP-Stack (5)
SetupAPI keys, accounts, billingcurl install | bash
ConfigTypeScript / YAML / subgraphOne TOML file
QueryCustom API / hosted subgraphAuto GraphQL API
# speed benchmarked on a Hetzner dedicated server, ethereum mainnet
sieve.toml
# that's the entire config
[[contracts]]
name = "USDC"
address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
abi = "abis/erc20.json"
start_block = 6_082_465
[[contracts.events]]
name = "Transfer"
table = "usdc_transfers"
context = ["block_timestamp", "tx_from"]
sieve inspect
# what you get
✓ Table usdc_transfers
├─ block_number BIGINT
├─ tx_hash TEXT
├─ log_index INTEGER
├─ from_address TEXT
├─ to_address TEXT
├─ value NUMERIC
├─ block_timestamp BIGINT
└─ tx_from TEXT
# chains

Five chains, one key in TOML

Each chain syncs over its own devp2p network. One Sieve process indexes one chain; run one per chain on the same host.

  • Ethereum1
    chain = "mainnet"
    1. full history
    2. live
  • Base8453
    chain = "base"
    1. archive
    2. history
    3. live
  • OP Mainnet10
    chain = "optimism"
    1. live
  • Unichain130
    chain = "unichain"
    1. live
  • World Chain480
    chain = "world"
    1. live

full history: Ethereum peers serve blocks back to genesis. archive: Base snapshot archives, then recent history from peers. live: new blocks as they arrive. Public OP Mainnet, Unichain and World Chain peers prune history; to backfill there, pin a node that serves it with trusted_peers.

# base: archive to live

Deep Base history, without a history node

Many Base peers prune old receipts. Sieve can start from Base's public snapshot archives, filter them down to your events, and then hand off to peers and follow live. One run, one checkpoint.

  1. 1. import · archive
    302,400 blocks
    7 days from Base snapshot archives
  2. 2. catch up · peers
    31,336 blocks
    from archive end to the tip over P2P
  3. 3. follow · live
    new blocks
    as the chain produces them
ARCHIVE · LAST BLOCK
#51,968,527
hash 0x3d2b32…8340
MATCH ✓
PEERS · FIRST BLOCK
#51,968,528
parent 0x3d2b32…8340
Zero gaps.
333,736 consecutive blocks · every parent hash linked
the measured run: one WETH/AAVE pool on Base →
# plan an import offline, no database or peers needed
terminal
sieve archive-plan --manifest base-manifest.json \
  --manifest-sha256 <64-hex-digit-digest> \
  --start-block 500123 --end-block 1000010
Base archive bootstrap docs →
sieve --capabilities
# what sieve can index
# backfill, catch up, follow live, on every chain
▸ events Filter by contract, event signature, indexed params
topic0 + address matching at sync time, bloom pre-screening
▸ calls Decode function calldata for specific selectors
only successful (non-reverted) transactions
▸ transfers Track native ETH transfers with address filters
no ABI needed, sender/receiver filtering
▸ factory Dynamically discover and index child contracts
auto-subscribe on PoolCreated, PairCreated, etc.
▸ graphql Auto-generated API with filters, pagination, sorting
_in, _not_in, _gte, _lte, OR composition
▸ stream Webhooks and RabbitMQ for real-time pipelines
per-event JSON messages, lazy reconnect
▸ chains Ethereum, Base, OP Mainnet, Unichain, World Chain
one chain key in TOML, own devp2p network each
▸ follow Backfill, catch up, then follow the head live
one command, no separate modes, /ready flips once caught up
▸ reorg Roll back and re-index when the canonical tip changes
up to 64 blocks, only on peer quorum, never on one peer
▸ archive Start Base from snapshot archives, then hand off to peers
pinned manifest + checkpoint hash, staging released per group
▸ verify Peer quorum on each segment's canonical hash
header chain linked + payload roots checked, no quorum no commit
# ready?
install
$ curl -fsSL https://raw.githubusercontent.com/slvDev/sieve/main/sieveup/install | bash