Ethereum Logs Hands-On with Ethers.js

Aw Kai Shin
5 min readOct 26, 2022

--

While developing on Ethereum, one of the key data types which you will inevitably comes across are smart contract events. Events provide a way for a smart contract to communicate with subscribing applications whether for the purpose of returning contract values or to trigger a corresponding function. Moreover, as every event is written to the blockchain, it can also serve as a historical log (and cheaper storage) of all the events emitted by a contract.

This article focuses on decoding this log function and assumes some basic knowledge on Ethereum events and logs. As a refresher:

  • Events are application specific as it is defined by the smart contract dev. For example, Larvalabs developers defines a PunkTransfer event to notify applications when a Cryptopunk has been transferred. The event is emitted when PunkTransfer() is called.
event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);
  • When querying logs for an event, the values emitted from the event are returned as a DataHexString. As such, we will need the contract ABI in order to decode the data (remember that the event is application specific). Below is the raw response when querying for the PunkTransfer event, the full specs can be found here.
  • Ethereum utilises bloom filters to store sections of the log data which enables for efficient querying and filtering of logs without the need to download the complete blockchain. “Topics” are used to describe the event with the first topic usually consisting of the keccak256 hash of the event signature (i.e. PunkTransfer(address,address,uint256)). Hence, topics enable the quick retrieval of specific logs.
Notice that this hash matches the first topic in the above screenshot

To better understand this, we will go through 2 ways of processing the logs, with and without the help of the Ethers.js contract convenience library. This guide will be querying the PunkTransfer events which have been emitted in the last 1000 blocks. By doing so, we will get a better appreciation of what Ethers.js is doing below the hood and how events are actually logged on Ethereum. The repository for this tutorial can be found here:

Decoding Raw Logs

In order to get the raw logs, we will be using the Ethers.js provider to query the blockchain without the added functionality of contract helpers. Once your provider has been configured and the latest block has been queried, we can get the raw logs by utilising provider.getLogs().

getLogs() expects a filter to be passed in as a parameter. In this case, we have passed in the CryptoPunks contract address, topic to filter by, as well as the filter block range. The full list of filter options can be found on the ethers official docs.

Notice that we have obtained the topic by hashing the eventSignature with ethers.utils.id, which returns the keccak256 hash. This points to the fact that events are stored as hashes on the blockchain in order to reduce storage requirements as all hashes are of fixed length. Additionally, as hashes are easy to compute based on an input but impossibly hard to reverse-engineer via an output, this also provides a certain level of transaction privacy.

As such, without knowing the contract from which the log originated from, we wouldn’t be able to know much about the event nor the topic to filter by. For reference, a sample raw response of the PunkTransfer event is provided below:

From the above screenshot alone, we are unable to obtain any useful data that is specific to the application. However, as we are aware of the event context, we can also decode it using the ABI (Application Binary Interface).

The ABI provided specifies how to interact with the raw event log. By instantiating an Interface object with the ABI, we can then decode the raw log with parsedLog() which results in the following:

The raw log has been successfully parsed and we are able to extract the values which were emitted by the smart contract via the PunkTransfer event.

Ethers.js Convenience Library

As we now have a better understanding of how logs are handled, we can better appreciate the abstraction which Ethers.js enables via the Contract object. We first create the CryptoPunk contract object:

Notice that in creating the contract, we have also provided the contract address and the complete ABI (which can be found on Etherscan). With the contract instance configured, getting the logs can be simply done via a simple queryFilter() function.

This method enables us to avoid the complexities of managing the topics when querying for an event as we only need to pass in the name of the event. The end result is the same but it is much easier to extract the smart contract data:

Thanks for staying till the end. Would love to hear your thought/comments so do drop a comment. I’m active on twitter @AwKaiShin if you would like to receive more digestible tidbits of crypto-related info or visit my personal website if you would like my services :)

--

--

Aw Kai Shin

Web3, Crypto & Blockchain: Building a More Equitable Web | Technical Writer @FactorDAO | www.awkaishin.com