Why events exist
Storage is expensive. Events let contracts emit structured data that clients and indexers consume without reading every storage slot. Logs are not accessible from contract code — they are for off-chain consumers. Historical queries use eth_getLogs.
- Up to 4 topics per log: topic0 is event signature hash, topics 1–3 index up to 3 parameters.
- Non-indexed parameters ABI-encode into the data field.
- Anonymous events omit topic0 — rare in practice.
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Transfer(address indexed from, address indexed to, uint256 value)
// topic0 = keccak256("Transfer(address,address,uint256)")
// topic1 = from, topic2 = to, data = valueDesigning event schemas
Index fields you filter on (user address, token id, proposal id). Pack small metadata in non-indexed data to save topic slots. Stable event names and parameter order matter — indexers key on signature hashes. Breaking changes require new subgraph versions.
- indexed strings are hashed — you cannot recover the string from the topic alone.
- Too many indexed fields reduces flexibility — max three indexed args per event.
- Emit events for every state change users need in a UI.
Filtering with eth_getLogs
Filters combine address (contract), topic0 (event sig), and optional topic1–3 equality. Topic arrays can use OR semantics per position with [hashA, hashB]. Block range is required — unbounded queries are rejected by public RPCs.
- fromBlock/toBlock windows must respect provider limits (often 2k–10k blocks).
- Reorgs can remove logs — indexers handle chain rewind or use finalized blocks.
- Multiple contracts need multiple queries or a factory address pattern.
Indexers and subgraphs
The Graph, Ponder, Envio, and custom workers ingest logs into databases. They map events to entities (User, Pool, Proposal) and serve GraphQL or REST. Correctness depends on handling reorgs, missed blocks, and contract upgrades that change ABIs.
- Start indexing at the deployment block — earlier ranges waste RPC quota.
- Proxy upgrades may change event layout — version your handlers.
- Call handlers can enrich with eth_call at emit time — mind archive node needs.
Merkle trees and claims
Airdrops and allowlists often emit a Merkle root on-chain while leaves stay off-chain. Users submit proofs at claim time. Indexers track Claimed events to show remaining eligibility. Ethereum Toolset's Merkle tools help generate roots and proofs consistently with on-chain verify functions.
- Leaf encoding (double hash vs single) must match the contract exactly.
- Sorted pairs vs unsorted Merkle implementations differ — copy battle-tested OZ patterns.
- One leaf claimed does not remove others — track per-leaf bitmaps in storage.
Security and privacy notes
Everything emitted is public forever — never log secrets, PII you should not publish, or raw seeds. Events can leak MEV-relevant intent before state changes finalize in the same tx (still visible in simulation). Admin events (OwnershipTransferred) should trigger monitoring alerts.
- Indexed emails still hash to public topics — do not emit personal data.
- Fake events are impossible from other contracts at your address — but impersonation contracts mimic ABIs elsewhere.
- Pause and role change events are incident-response signals.
