How to monitor blockchain events with ReactJS and Web3.js

In web applications, we often send a request to a server and it takes a considerable amount of time to be processed. When we don't know how long that will take and can't keep the connection open until we get a response without risking a timeout, we have to think of alternatives. The simplest and least efficient alternative is to do "polling", a technique that consists of creating a timer and repeatedly making requests to the server asking for updates. The best alternative is to try to be notified by the server somehow when processing finishes, usually involving websockets or webhooks.
In blockchain programming, we have this same problem raised to the nth power, since processing on network nodes is expensive and usually slow, at least on the most popular networks like Ethereum. Even if you're using a cheaper network like BNB Smart Chain (BSC) or Polygon, you'll still face this dilemma: how do you know a process has finished without repeatedly querying the blockchain?
In this specific universe, the solution is to monitor/listen for specific events in smart contracts while waiting for updates, and that's what we'll explore in today's tutorial, using the Web3.js library and the ReactJS library.
#1 – Events in Solidity
The first point is to understand how events work in Solidity, assuming here that you'll be working with Ethereum/EVM-based blockchains. When we're programming our smart contracts, we can use an event keyword to declare an event, as shown below, taken from an ERC-20 token contract.
event Transfer(address indexed from, address indexed to, uint256 value);This Transfer event is declared in the contract and indicates that it can be monitored/listened to by anyone connected to the blockchain. Now, for the event to actually happen, to be triggered, the smart contract developer must use the emit keyword in some function of the contract, as shown below, taken from the same ERC-20 contract.
emit Transfer(msg.sender, to, value);So, when we call emit Transfer, we send an event record to the blockchain that will be included along with the logs of the mined block, and any system monitoring that specific event will be notified, like a cryptocurrency wallet, for example.
One last point about events in Solidity is that you may want to listen to the events of a specific wallet, instead of listening to any transfer that happens. This is possible by using the indexed keyword as shown above, where both from and to can be used as a filter in monitoring to receive only events from a specific from or to. It's important to note that using the indexed keyword increases the gas cost of transactions that emit this event, and there's also a limit of up to 3 indexed variables per event, so use it sparingly.
Although the example above is quite common, involving tokens and transfers, you can create whatever event you want for any purpose. For example, if you have a smart contract for a dex (decentralized exchange), you might want to have an event for when a trade is made between two traders, as shown below.
event NewTrade(uint timestamp, address indexed from, address indexed to, uint amount);Notice that this event has nothing to do with the previous one, its fields are completely different. Then, in the function that actually makes the trade happen, you emit this event to notify whoever is monitoring this event on this contract.
emit NewTrade(block.timestamp, msg.sender, to, amount);That said, now that you understand how events are declared and emitted in contracts, it's time to understand how we monitor them in our web applications.
#2 – Monitoring events on the frontend
In this tutorial, I'll use the Web3.js library since it's the oldest one, but there are currently better alternatives like EthersJS and Viem.
For our frontend web application example, I'll use ReactJS, a technology widely used in Web3 dapps. Even without any knowledge of React, you can create a simple project using the command below on your machine, choosing the name dapp-event-web3, selecting React and JavaScript.
npm create vite@latestThen, go into the project folder and let's install the web3.js library we'll be using.
npm i web3Now, with everything set up, let's create a feature on the frontend (App.jsx) that waits for a token's transfer event. For you to be able to simulate this test, it's important that you've already deployed a contract of your own on the blockchain, so you can interact with it in a way that triggers the event you'll be monitoring. I'll be using this ERC-20 token contract that I deployed on the BSC Testnet.
To connect to your contract, you'll need its ABI (Application Binary Interface), which you get after compiling it or after verifying it on BSCScan/EtherScan. My ERC-20 contract's ABI is below, and it defines the entire public specification of the contract.
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]With your ABI in hand, create an abi.json file in the src folder of your frontend project and import it at the top of the App.jsx file along with the Web3 package as well.
import Web3 from 'web3';
import ABI from './abi.json';To receive blockchain events, it's important to have a websocket connection to a full node. Fortunately, nowadays it's very easy to get access to one using blockchain-as-a-service providers like Infura, Alchemy, Ankr, Quicknode, and others that offer free limited access to them. In my case, since I have the contract deployed on the BSC Testnet, I'll use Alchemy in this example, which is one of the providers that offer websocket nodes for BNB Chain. Just create an account with the provider of your choice and use the free node to get the websocket server URL, which we'll need further below.
This URL, as well as your contract's address on the blockchain, should be placed in a .env file at the root of your project, which shouldn't be version-controlled, as shown below.
VITE_WEBSOCKET_URL=<your url>
VITE_CONTRACT_ADDRESS=<your contract>With the imports done and the environment variables set, let's write the function inside App() that will handle monitoring and processing the event after it's received.
async function doListen() {
const web3 = new Web3(import.meta.env.VITE_WEBSOCKET_URL);
const contract = new web3.eth.Contract(ABI, import.meta.env.VITE_CONTRACT_ADDRESS);
contract.events.Transfer({
filter: { to: "0x576906e0321cd8d57928aef6b3e9e81cd6d0ecef" },
fromBlock: "latest"
})
.on('data', console.log)
}We start the function by loading a Web3 object with the websocket server URL. Then, using this object, we initialize a contract configured with the ABI and the address of the contract we've deployed on the blockchain.
Once this connection to the contract is set up, we access its events down to an object with the exact same name as the event, just as it appears in the ABI, Transfer in this case. Optionally, you can pass a configuration object to filter one of the event's indexed parameters, where I chose "to" in this example. In other words: I'll only monitor the contract's transfer events where funds were sent to that specific wallet (change it to one of your choosing or modify the filter). The fromBlock setting, in turn, tells it that we only want to monitor events from the most recent block, since it's also possible to query events triggered in the past.
Finally, we set up an event listener for data using the 'on' function. The callback we pass to it will be triggered every time a new event is received via websockets. Now put this function on the click of some button on the screen, as shown below, and you'll be able to test it easily as long as you can simulate the situation that will trigger the event. To run a ReactJS application, just use the 'npm run dev' command.
function btnClick() {
doListen()
.then(() => console.log("listening"))
.catch(err => console.error(err))
}
return (
<div>
<button type='button' onClick={btnClick}>Listen Event</button>
</div>
);In my test, I transferred a coin from my main wallet to the wallet that's in my event's filter, in order to see the logs arriving printed in the Google Chrome console. Then, when I repeated the test but sending a coin to some other wallet, I didn't receive the event in the console.
And with that, we've finished our tutorial on how to monitor blockchain events with Web3.js. I hope it helped you!
Translated from the Brazilian Portuguese original · Read the original

