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¶
- State model — projection keys and authoritative ownership.
- Ordering — normal versus forced call order and double-application hazards.
- Replay recipes — packed L3/L2, balances, passive liquidity, Perp positions/OI, liquidations, intents, and hooks.
- Bootstrap and versioning — discovery, proxy/route intervals, reorgs, checkpoints, and historical decoders.
- 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...Requestedaudit records on top of their authoritative paired snapshots. - Buffer unknown proxy logs within a deployment transaction:
Upgraded,OwnershipTransferred,Initialized, andAuthorityUpdatedcan 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.