How to listen to smart contract events using Web3.JS and JavaScript

In this tutorial, we are going to learn how to listen to smart contract events and run code when the event is triggered and a log is added using Web3JS and JavaScript.

In this tutorial, we are going to learn how to listen to smart contract events and run code when the event is triggered and a log is added using Web3JS and JavaScript.

What is an event in smart contracts

Smart contracts can define events and trigger them which generates logs on the blockchain.

These logs have a specific type (or name) and when they are emitted, smart contracts pass in parameters to add data to the log.

Smart contracts logs are used for multiple reasons:

  • It's a cheap form of storage, it costs 8 gas per byte whereas contract storage costs 20,000 gas per 32 bytes (but not all smart contracts can access all logs)
  • It allows smart contracts to trigger changes in front-end applications

As you may have guessed, we are going to focus on the second use case.

We are going to see how to run JavaScript code when these logs are generated using the Web3JS library.

How to listen for event logs of a specific type

To subscribe to an event and run code when logs are generated in the smart contract, we use the events property of the contract object and the name of the event like a function:

const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS);

contract.events.MyEvent({
    filter: {eventParam: [20, 23]}, // 20 or 23
    fromBlock: "earliest" // block number to start listening from
}, (error, event) => {
    // fired when we get a new log that matches the filters for the event type we subscribed to
    if (!error) {
        console.log(event);
    }
})
.on("connected", (subscriptionId) => {
    // fired after subscribing to an event
    console.log(subscriptionId);
})
.on('data', (event) => {
    // fired when we get a new log that matches the filters for the event type we subscribed to
    // will be fired at the same moment as the callback above
    console.log(event);
})
.on('changed', (event) => {
    // fired when the event is removed from the blockchain (it adds this property on the event: removed = true
})
.on('error', (error, receipt) {
    // fired if the subscribe transaction was rejected by the network with a receipt, the second parameter will be the receipt.
});

In the code above, I listen to the events of type MyEvent that are emitted from the earliest block (the first one) and that have 20 or 30 as values for the eventParam parameter.

The event method will take in parameters:

  1. An object to filter the events to listen to (optional)
  2. A callback function (optional) to run when an event is emitted. That function takes 2 parameters, first the error if there is one and second the event object (more about this below)

The filters object has the following properties:

  • filter (optional object): Filter events by indexed parameters. For example:
filter: { myParam1: 123, myParam2: [1, 2] }

Indexed parameters are defined when the event is defined in the smart contract.
In the logs, all parameters are indexed parameters, there can only be 3 indexed parameters per type of event.

  • fromBlock (optional number or string): The block number from which to get events.

Instead of passing a block number, you can also pass the following strings:
"earliest", "latest" and "pending"

  • topics (optional array): This allows to manually set the topics for the event filter.

Event topics are indexes that you can use to filter your events. There can only be up to 4 topics in an event, the first one being the hash of the log and the 3 others being the indexed parameters.
If you pass the filter property, it will automatically set the topics property according to the indexed parameters that you want to use to filter the logs.

Once you subscribe to an event using the code above, you can use the following event listeners to trigger code depending on what happens using the .on method:

  • "connected": fired after subscribing to an event. Takes in parameter the subscription ID
  • "data": fired when we get a new log that matches the filters for the event type we subscribed to. Takes in parameter the new event that was emitted (more on that object below)
  • "changed": fired when the event is removed from the blockchain. It takes in parameter the event that was removed and adds this property to the event: removed: true
  • "error": fired if the subscribe transaction was rejected by the network. It takes in parameter the error and a receipt in the second parameter.

The Event object

An event returned in Web3JS will have this format:

  • event: The event name.
  • signature: The hash of the event, null if it’s an anonymous event.
  • address: The address of the smart contract that emitted the event.
  • returnValues: The values returned by the event (an object)
  • logIndex: The event's index position in the block (integer)
  • transactionIndex: The transaction’s index position in the block where the event was created in (integer)
  • transactionHash: The hash of the transaction this event was created in.
  • blockHash: The hash of the block this event was created in. It will be null if it’s still pending.
  • blockNumber: The block number this log was created in. It will be null if it's still pending.
  • raw.data: The data containing the non-indexed log parameters.
  • raw.topics: An array containing the topics of the event.

How to listen for event logs of a specific type once

If you only want to subscribe to an event and unsubscribe as soon as the event is fired or when there is an error, you can use the once function:

const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS);

contract.once('MyEvent', {
    filter: {eventParam: [20,23]},
    fromBlock: "earliest"
}, (error, event) => {
    if (!error) {
        console.log(event);
    }
});

The once function takes in parameter the name of the event, an objet containing filters (same parameters as above) on the incoming parameters and a callback to execute when there is an event that matches the filters that is emitted or if there is an error.

The event object returned has the format described above.

How to get the past events of a smart contract

To get the past logs of a smart contract, we use the getPastEvents or events.allEvents functions.

The getPastEvents function takes in 2 parameters:

  1. A string containing the name of the event type to get or "allEvents" if you want to get all types of events
  2. An object to filter the search (the same as described above)

Here is an example based on the smart contract above:

const contract = new web3.eth.Contract(ABI, contractAddress)

contract.getPastEvents('Message', {
    filter: {recipient:"0x5b8f1310A956ee1521A7bB56160451C786289aa9"},
    fromBlock: 0,
    toBlock: 'latest'
})
.then((events) => {
    console.log(events)
});

In this example, I use the filter parameter to filter the events and get only the ones where the recipient is 0x5b8f1310A956ee1521A7bB56160451C786289aa9. It searches from the first block to the latest.

To get all the events you can also use allEvents which works just like the getPastEvents function:

const events = await contract.events.allEvents({
    // same options as above
})

And that's it 🎉

Thank you for reading this article