James Bachini

Building Smart Contract Event Filtering Systems for DApps

Events Solidity Ethers.js
  1. How events work in web3
  2. Simple event emitter contract
  3. Setting up Ethers.js with React
  4. Subscribing to events
  5. Filtering the event data
  6. Event based state changes

How events work in web3

Events in Web3 act as logs of significant occurrences within a smart contract. When a specific function is executed, the smart contract can “emit” an event, signalling to external systems that something has happened. These emitted events are stored on the blockchain, but they do not modify the contract’s state. Instead, they serve as off-chain notifications that can be accessed through tools like Ethers.js or directly from blockchain explorers.

Every event contains two types of information:

  • indexed parameters allow filtering of events more efficiently, as they are stored in a way that enables searching through a large number of events without needing to examine the entire blockchain.
  • non-indexed parameters provide additional information but cannot be directly used for filtering.

In essence, events are vital in Web3 systems because they provide a structured mechanism for communicating what has happened on-chain to the off-chain world, especially front-end applications that interact with smart contracts.


Simple event emitter contract

Let’s deploy a simple Solidity smart contract that emits events using Remix. I’ve done this for you if you want to skip this step you can use this address on Sepolia:

0x3B46C27CC1c13230e0F7336e09f1f261AE672066

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract EventEmitter {
    event Transfer(address indexed from, address indexed to, uint256 amount);

    function transfer(address to, uint256 amount) public {
        // Perform transfer logic here
        emit Transfer(msg.sender, to, amount);
    }
}

In this contract, we define a Transfer event with three parameters. The indexed keyword allows efficient filtering of events based on these parameters. When the transfer function is called, it emits a Transfer event with the relevant data.

The contract ABI for this contract is here:

[{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"}]

There’s more in depth information about Solidity events here:


Setting up Ethers.js with React

To interact with smart contracts from a React application, we’ll use Ethers.js. First, install the necessary dependencies:

npm install ethers react react-dom

Next, create a new React component and import Ethers.js:

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';

function EventListener() {
    const [events, setEvents] = useState([]);

    useEffect(() => {
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const contractAddress = '0x3B46C27CC1c13230e0F7336e09f1f261AE672066';
        const contractABI = [...]; // Your contract ABI

        const contract = new ethers.Contract(contractAddress, contractABI, provider);

        // Event listening logic will be added here

    }, []);

    return (
        <div>
            {/* Event display logic */}
        </div>
    );
}

export default EventListener;

This setup creates a basic React component that connects to the Ethereum network using Ethers.js.


Subscribing to events

To subscribe to events, we’ll use the on method provided by Ethers.js. Add the following code within the useEffect hook:

contract.on('Transfer', (from, to, amount, event) => {
    const newEvent = {
        from,
        to,
        amount: amount.toString(),
        transactionHash: event.transactionHash
    };
    setEvents(prevEvents => [...prevEvents, newEvent]);
});

return () => {
    contract.removeAllListeners();
};

This code listens for Transfer events and updates the component’s state with new event data. The cleanup function ensures that we stop listening when the component unmounts.


Filtering the event data

Ethers.js provides powerful filtering capabilities. Let’s modify our code to filter events based on specific criteria:

const filter = contract.filters.Transfer(null, '0x...');

contract.on(filter, (from, to, amount, event) => {
    // Event handling logic
});

This example filters Transfer events where the recipient is a specific address. You can create more complex filters by combining multiple parameters.


Event based state changes

Events can trigger state changes in your DApp, providing real-time updates to users. Here’s an example of how to update a user’s balance based on Transfer events:

function EventListener() {
    const [balance, setBalance] = useState('0');

    useEffect(() => {
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const contractAddress = '0x3B46C27CC1c13230e0F7336e09f1f261AE672066';
        const contractABI = [...];
        const contract = new ethers.Contract(contractAddress, contractABI, provider);

        async function updateBalance(address) {
            const newBalance = await contract.balanceOf(address);
            setBalance(newBalance.toString());
        }

        contract.on('Transfer', (from, to, amount, event) => {
            if (from === userAddress || to === userAddress) {
                updateBalance(userAddress);
            }
        });

    }, []);

    return (
        <div>
            <p>Current Balance: {balance}</p>
        </div>
    );
}

This code updates the user’s balance whenever a Transfer event involves their address, ensuring the DApp always displays the most current information.

In production we might want to cross check the balanceOf(address) function in an ERC20 contract to ascertain a user’s balance whenever the Transfer event was fired.



Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.