Introduction to Solidity : Events

We are almost at the end of this series. In this post we will learn about events, how to emit them and how to listen to them.

Events

Events are an abstraction of the EVM logging functionality. They are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.

They are inheritable members of contracts, when they are called their arguments are stored in the transaction log. This means that they can be accessed using the address of the contract and the transaction hash as long as the block is accessible. But log and event data are not accessible inside the contract.

It's possible to add the indexed keyword up to three parameters of an event. This means that parameters will be stored as a topics instead of data. This allows you to filter by this parameters.

Here's an example of a contract that emit an event :

pragma solidity ^0.8.0;

contract MyContract {
    event NewUser(address indexed userAddress, string username);

    function register(string memory _username) public {
        // Register the user
        emit NewUser(msg.sender, _username);
    }
}

In this example, the event NewUser is emitted when the function register is called. The event takes two parameters, the address of the user and the username. The indexed keyword is added to the first parameter, which means that the address of the user will be stored as a topic instead of data.

It's important to not that topic can only hold a single word (32 bytes), so if you use a reference type as an indexed parameter, this is the Keccak-256 hash of the value that is stored as a topic instead. All parameters without the indexed attribute are ABI-encoded into the data part of the log.

The hash of the signature of the event is one of the topics, except if you declared the event with the anonymous specifier. This means that it is not possible to filter for specific anonymous events by name, you can only filter by the contract address. The advantage of anonymous events is that they are cheaper to deploy and call. It also allows you to declare four indexed arguments rather than three.

Since the transaction log only stores the event data and not the type, you have to know the type of the event, including which parameter is indexed and if the event is anonymous in order to correctly interpret the data.

event.selector is for non-anonymous events, a bytes32 value containing the keccak256 hash of the event signature, as used in the default topic.

How to listen to events

First let's write an sample contract that emit an event :

pragma solidity ^0.8.0;

contract MyContract {
    event NewUser(address indexed userAddress, string username);

    function register(string memory _username) public {
        // Register the user
        emit NewUser(msg.sender, _username);
    }
}

Next to listen the event we will use wagmi and the hook useContractEvent. If you want to know how to install and use wagmi you can read this post.

Here how to use the hook :

import { useContractEvent } from 'wagmi'

function App() {
  useContractEvent({
    address: '0x00000...e1e',
    abi: MyContractABI,
    eventName: 'NewUser',
    listener(userAddress, username) {
      console.log(userAddress, username)
    },
  })
}

The hook takes an object as parameter. The object has the following properties :

  • address : The address of the contract
  • abi : The ABI of the contract. It's optional but if provided TypeScript will infer the correct types for eventName and listener.
  • eventName : The name of the event to listen to in our NewUser
  • listener : The function to call when the event is emitted. The function takes the same parameters as the event.

You can check the wagmi doc for more informations on the hook config.

Conclusion

That's it for the event part of this series. Now you know how to emit events from a contract and how to listen to it from your application. In the next post we talk about Inheritence.