Skip to content

Events and indexing

Use this section to turn contract logs and calldata into deterministic projections. It separates event-reconstructable state from values that require transaction input, replay, or block-pinned getters.

What to read

  1. State model — projection keys and authoritative ownership.
  2. Ordering — normal versus forced call order and double-application hazards.
  3. Replay recipes — packed L3/L2, balances, passive liquidity, Perp positions/OI, liquidations, intents, and hooks.
  4. Bootstrap and versioning — discovery, proxy/route intervals, reorgs, checkpoints, and historical decoders.
  5. Fixture requirements — receipt/log scenarios and expected projections.

Minimum production rules

  • Process canonical logs by (blockNumber, transactionIndex, logIndex, packedRecordIndex).
  • Key decoders by (chainId, emitter, abiVersion, abiSchema, topic0). Spot and Perp reuse packed event signatures with different record widths and flags.
  • Store raw log bytes, transaction input, block hash, decoder version, and implementation address before applying a reducer.
  • Treat snapshot events as replacements. Do not add Deposit, Withdrawal, PerpMarginTransferred, PerpFeeCharged, or AccountCore ...Requested audit records on top of their authoritative paired snapshots.
  • Buffer unknown proxy logs within a deployment transaction: Upgraded, OwnershipTransferred, Initialized, and AuthorityUpdated can precede the Router discovery log.
  • Roll back every derived row from an orphaned block before applying a replacement branch.
  • Query a documented getter at the event block after transaction execution. A block-end getter cannot distinguish two transactions in the same block; use calldata, trace/state-diff support, or replay when that distinction matters.

Boundaries that must not be guessed

State Authoritative treatment
AccountCore protocol pause and fee collector toggleProtocolState and setFeeCollector have no semantic update event. Decode successful admin calldata and reconcile protocolPaused() / feeCollector() at that boundary.
Perp risk configuration PerpMarketUpdated and PerpMarketRiskParamsUpdated omit most values. Decode registration/update calldata and verify PerpEngine.getMarket(marketId) at the same boundary.
Cross positions No generic cross-position snapshot exists. Replay exact fill/exposure/mode/slot transitions or bootstrap from block-pinned getCrossPosition.
Open interest No dedicated event exists. Derive long/short OI from before/after signed position magnitudes or bootstrap from getMarket.
Spot protocol fees No dedicated Spot fee event exists. Use fee configuration plus settlement arithmetic and authoritative SpotReserveUpdated snapshots for the configured collector.
Passive fee growth PassiveBandUpdated, PassiveFeesClaimed, and burn records omit some live growth/checkpoint values. Use the documented exact replay or block-pinned passive getters.
Upgrade/route replacement Close the old implementation or route interval at the authoritative log position and select the new ABI only for subsequent logs. Never decode history with the current implementation alone.

The state model and replay recipes enumerate the precise event names and rules behind each row.