- How events work in web3
- Simple event emitter contract
- Setting up Ethers.js with React
- Subscribing to events
- Filtering the event data
- 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.