Farbeam
Docs / Core concepts

Indexing

One process runs one poll loop per chain. Each loop reads the chain head, stores any large transfers it finds, updates the status, then waits. This page describes the loops as they are coded, including what they miss.

#Poll intervals

The wait starts after a poll finishes, so the real cycle is the wait plus the time the RPC calls take. After a failed poll, the wait doubles, up to 60 seconds.

ChainWait after a good pollWait after a failureIndexes transfers
Ethereum12 s24 sCurated tokens and native ETH
Base16 s32 sCurated tokens
BNB Chain16 s32 sCurated tokens
Arbitrum One16 s32 sCurated tokens
OP Mainnet30 s60 sNone
Polygon PoS30 s60 sNone
Solana30 s60 sOne sampled block: curated tokens and SOL

#The EVM poller

  1. Send a batch of eth_getBlockByNumber("latest") and eth_gasPrice. On Ethereum, the block includes full transactions.
  2. Update head, block time, lag, transaction count, gas ratio and fees. Add the block to the in-memory list of 25.
  3. Stop here on OP Mainnet and Polygon.
  4. On Ethereum, scan the head block's transactions. Store each one whose value times the ETH price is at least $100,000. Skip contract creations.
  5. Call eth_getLogs for ERC-20 Transfer events of the curated tokens, from the block after the last scanned block, up to 200 blocks per poll.
  6. For each log, compute amount and usd. Store it if usd is at least $100,000.
  7. Move the cursor to the head.

Storage uses a unique uid. A row found twice is stored once, and it reaches rules and the stream once.

#The log scan cursor

The log range starts at the larger of cursor + 1 and head - 2000. It ends at the smaller of head and from + 199. The cursor then moves to the end of the range. A poll reads 200 blocks at most, which keeps each call small enough for public RPC limits. A chain that produces more blocks than that in one poll is caught up over later polls. No block is skipped unless the reader falls more than 2,000 blocks behind. scan_lag_blocks in Status shows the distance. The cursor is held in memory, so a restart begins again at the head.

This table was measured on 2026-11-04 UTC from the last 25 entries of Recent blocks. The log scan was changed after this measurement. All four chains fit inside the 200 block limit.

ChainSeconds between readsBlocks between readsResult
Ethereum13.51.1Inside the limit
Base17.38.7Inside the limit
BNB Chain17.338.5Inside the limit
Arbitrum One16.857.8Inside the limit

#The Solana poller

  1. Send a batch of getSlot with finalized commitment, getRecentPerformanceSamples and getRecentPrioritizationFees.
  2. Update TPS and the 75th percentile priority fee.
  3. Call getBlock for that slot. If the RPC returns an error for it, try up to three older slots.
  4. For each successful transaction in that one block, compute balance changes per owner for the curated Solana tokens, and per account for SOL.
  5. For each asset, take the largest decrease and the largest increase. amount is the smaller of the two. Store it if usd is at least $100,000.

Only one block per poll is read. A separate loop reads recent transactions of 7 public exchange wallets every 120 s with the same logic. Read sampling before you use these rows.

#What gets missed

CaseWhyEffect
Tokens outside the curated listThe log filter names curated contracts onlyNever stored
Transfers below $100,000The threshold applies at index timeNever stored
Blocks more than 2,000 behind the headDescribed aboveLost
Blocks passed during a restartThe cursor is held in memoryLost
ETH in a block between two pollsThe native scan reads only the head blockLost. The Ethereum read gap was 2 blocks now and then in the measurement above.
ETH moved inside a contract callThe scan reads top-level value only, with no tracesNever stored
Native assets on Base, BNB Chain and ArbitrumNative indexing is on for Ethereum and Solana onlyNever stored
All of OP Mainnet and PolygonNo index on these chainsHead, fees and lookups only
Solana slots that were not sampledOne slot per pollMost Solana activity is absent
Time while the service is downThe cursor lives in memory, and a new start begins at the current headLost. No backfill.
Time while the price is unknownA row needs a price. With no price since start, the row is skipped.Lost
Reorganized blocksRows are not removed after a reorg, and EVM polls read latestA row can refer to a block that is no longer canonical

The Ethereum native scan checks the receipt of each transaction at or above the threshold. A reverted transaction is not stored. If the receipt read fails, the row is not stored either.

ERC-20 rows come from logs, and logs exist only for successful transactions.

#Other timers

TimerIntervalEffect
Price refresh60 sUpdates the CoinGecko prices used for valuation
Status sample60 s, stored in 5 minute bucketsFeeds Status history. The last write in a bucket wins.
Prune1 hourDeletes transfers older than 7 days, samples older than 30 days and delivery rows older than 14 days
Webhook worker3 sSends up to 20 due deliveries

#How to use this data

  • Treat the index as a filter of large movements in a short list of tokens. It is not a full copy of any chain.
  • Use sampled, chain and this page's table when you compute totals.
  • Read indexed_since_start and last_ok from Status to tell whether a quiet period means a quiet chain or a stalled poller.