iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Events

Solidity events let contracts log to the blockchain. Cheaper than storage; indexed for off-chain searching. Front-ends and indexers (TheGraph, Subsquid) subscribe to these to build user-facing data.

Define, emit, index, subscribe

EXAMPLE
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract Marketplace {
    // 1) Define events — 'indexed' fields are searchable; max 3 per event
    event ItemListed(uint256 indexed id, address indexed seller, uint256 price);
    event ItemSold  (uint256 indexed id, address indexed buyer,  uint256 price);
    event ItemDelisted(uint256 indexed id);

    // Bulk events — for analytics
    event Stat(string  indexed kind, uint256 value);

    struct Item { address seller; uint256 price; bool active; }
    mapping(uint256 => Item) public items;
    uint256 public nextId;

    function list(uint256 price) external {
        require(price > 0, "price must be > 0");
        uint256 id = ++nextId;
        items[id] = Item(msg.sender, price, true);
        emit ItemListed(id, msg.sender, price);
    }

    function buy(uint256 id) external payable {
        Item storage it = items[id];
        require(it.active,           "not for sale");
        require(msg.value == it.price, "wrong amount");
        it.active = false;
        (bool ok, ) = it.seller.call{value: msg.value}("");
        require(ok, "payment failed");
        emit ItemSold(id, msg.sender, it.price);
    }

    function delist(uint256 id) external {
        require(items[id].seller == msg.sender, "not seller");
        items[id].active = false;
        emit ItemDelisted(id);
    }
}

// === Off-chain — ethers.js v6 ===
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider(RPC_URL);
const contract = new ethers.Contract(ADDRESS, ABI, provider);

// 2) Listen for new events
contract.on('ItemListed', (id, seller, price, event) => {
    console.log('listed', id, seller, ethers.formatEther(price));
});

// 3) Listen with a filter — only the seller you care about
const filter = contract.filters.ItemListed(null, sellerAddress);
contract.on(filter, (id, seller, price) => { /* … */ });

// 4) Query historical events
const from = (await provider.getBlockNumber()) - 50_000;
const events = await contract.queryFilter('ItemListed', from, 'latest');
for (const e of events) {
    console.log(e.args.id, e.args.price);
}

// 5) Decode a raw log (e.g. when reading from a tx receipt)
const receipt = await provider.getTransactionReceipt(txHash);
for (const log of receipt.logs) {
    try {
        const parsed = contract.interface.parseLog(log);
        if (parsed?.name === 'ItemSold') console.log('sold', parsed.args);
    } catch { /* not our event */ }
}

// === Indexing at scale — TheGraph subgraph ===
// subgraph.yaml
// dataSources:
//   - kind: ethereum/contract
//     name: Marketplace
//     network: mainnet
//     source:
//       address: '0x...'
//       abi:     'Marketplace'
//       startBlock: 12345678
//     mapping:
//       kind:        ethereum/events
//       apiVersion:  0.0.7
//       language:    wasm/assemblyscript
//       entities:    [Listing, Sale]
//       eventHandlers:
//         - event:  'ItemListed(indexed uint256, indexed address, uint256)'
//           handler: 'handleListed'
//         - event:  'ItemSold(indexed uint256, indexed address, uint256)'
//           handler: 'handleSold'
//       file: ./src/mapping.ts

// === Gas hints ===
//   • Events cost ~375 gas + 8 gas/byte + 375 per indexed topic
//   • Storing the same data in state is ~20k gas per write — events are 50x cheaper
//   • Don't put primary data in events — they're discoverable but not contract-readable

// === Best practices ===
//   • Index the fields users + dashboards filter on (id, owner, kind)
//   • Include enough context that off-chain subscribers don't need a follow-up read
//   • Emit on every state change worth observing — UIs depend on it
//   • Version events when ABIs change (NewEventV2) — don't reuse a signature

Why it matters

Events are the contract’s API to the off-chain world. Get them right and your UI / indexer needs ~zero RPC calls; get them wrong and every component re-fetches state by hand. Index the fields users filter on; emit on every state change.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
event Transfer(address indexed from, address indexed to, uint256 amount);

function send(address to, uint256 amount) external {
    // …
    emit Transfer(msg.sender, to, amount);
}
Try it Yourself »

Exercise

Emit an event named Transfer.

Transfer(msg.sender, to, amount);

Discussion

Loading…