- Shows how Ethereum events in Solidity serve as logs, capturing contract activity (e.g. NFT transfers).
- Explains the event signature and parameter indexing, plus how to find them on Etherscan or through hashing with keccak256.
- Demonstrates fetching logs via both curl and web3.js – either by specifying a block range and contract address, or by filtering on event signatures, addresses, and token IDs.
- Emphasizes the importance of logs for analyzing, debugging, and auditing smart contract transactions in production.
Main article
Imagine that you are holding a meeting to discuss the progress of a project. During the meeting, you touch upon various topics, make decisions, assign tasks, and, maybe even partake in some friendly banter. Once the session is adjourned, a minute of the meeting will be prepared and it will serve as a record of all the important things that were discussed and decided during the meeting. This will include information such as the date and time of the meeting, the attendees, the agenda, and the minutes of each discussion point (no, you don’t include the banter). In programming, logs have a similar purpose. They are used to record important events and activities that occur within the program. For example, when a customer makes a purchase from an online store, the program can record the event by writing a log entry to a file or database. This log entry would contain information such as the date and time of the purchase, the customer’s information, the item purchased, and the price. These entries will act as valuable sources of information for debugging, troubleshooting, and understanding how the program works. And just like meeting minutes, logs in programming can also be used to look back at the history of events and activities that have occurred within the program. In Web3, people who have dabbled in the art of writing Solidity programs know that Solidity doesn’t support any explicit form of logging. You can’tprint
or display
the data from within a Solidity program. So how do you keep track of the happenings within a Solidity smart contract? Well, the workaround for this comes in the form of events.
You see, Ethereum (or the Ethereum Virtual Machine (EVM), to be exact) supports the storage of specifically indexed data structures and this feature is conveniently called logs. The EVM logs allow for the recording and storage of events and activities on the Ethereum network. These logs can be accessed and analyzed by developers, allowing them to gain a deeper understanding of the activities taking place on the Ethereum network. This logging feature is leveraged by Solidity in order to implement events.
Just like the print
statements in other languages, events
in Solidity are a way for smart contracts to communicate with the outside world and to record important information on the Ethereum network. Events are useful for a variety of purposes, such as tracking the execution of transactions, monitoring the state of a contract, and providing notifications to other contracts or external applications. Understanding how to fetch and utilize these events will enable developers to better audit and monitor the behavior of smart contracts on the network and this article is essentially a guide that will help you do just that. So, LFG!!!
Understanding event logs
In Solidity, an event is a way to log information or data that can be read by external clients. Events are declared using theevent
keyword, and they allow smart contracts to send asynchronous notifications to clients. Events can be used to notify users of changes in the contract state, and they can be filtered, searched, and archived for future reference.
An event can have parameters, which are typed variables that provide additional information about the event. To emit an event in a Solidity function, use the emit
keyword followed by the event name and its parameters. Event logs are stored in the blockchain, and they can be accessed through APIs or block explorers. The following code demonstrates the declaration and emission of events in Solidity:
- Event signature — a unique identifier for the event, which is generated by hashing the event name and its parameter types using the Keccak-256 hash function.
- Contract address — address of the smart contract that emitted the event.
- Topics — an array of indexed parameters that provide additional information about the event. Each event can have up to 4 topics in the array, which can be used to filter and search for specific events. The first topic is always the event signature.
- Data — the field where every non-indexed parameter in an event is added to. It can contain any type of information, including strings, numbers, or arrays. The data is encoded as hexadecimal values.
- Block number — the number of blocks in the blockchain where the event was emitted.
- Transaction hash — the hash of the transaction that triggered the event.
Prerequisites
Before you start writing the code, make sure you have the following components installed on your system:- Node (Version ≥ 16) and the corresponding npm
- curl (https://curl.se/download.html)
- A code editor (VS Code, preferably)
- Create a new directory.
- Open a terminal in the directory.
-
Run the following command to initialize a Node project:
- Provide the details for the project, as per the prompt.
-
Once the project is initialized, use the following command to install the web3.js package:
Decoding the logs using Bored Apes
The Bored Ape Yacht Club (BAYC) is a non-fungible token (NFT) collectible based on the Ethereum blockchain. The BAYC contract is the smart contract that manages the creation, transfer, and ownership of the Bored Ape Yacht Club NFTs. Each Bored Ape Yacht Club NFT is unique and can be bought, sold, and traded on various NFT marketplaces. The BAYC contract enforces the rules of ownership and transfer of the NFTs, ensuring that each NFT can only be owned by one person at a time. The BAYC contract also includes events that are emitted whenever a transfer of an NFT occurs. These events can be monitored by external clients to track the state of the BAYC contract and keep track of transfers that have taken place. To understand the event logs better, let’s take a look at the BAYC contract’sTransfer
event :
from
, to
, and tokenId
—and all of them are preceded by the indexed
keyword.
Upon emitting this event, an event log will be created and within that log, the event signature will be generated by hashing (using Keccak-256) the name (Transfer
) and parameter types (address
, address
,uint256
) of the event. Below is the web3.js code for generating the event signature for the Transfer
event:
topic[0]
of every log.
The topics
array represents all the event parameters that are marked using the indexed
keyword: from
, to
, and tokenId
, in our case. These topics help query and identify particular event logs. The topics
array can include a maximum of 4 elements (topics) and the first element in the array will always be the event signature.
Each topic (parameter data) in the array can be represented using a maximum of 32 bytes of data and while representing the data, if the data falls short of 32 bytes, you can add filler data to cover the size specification.
Any topic carrying data exceeding the prescribed size limit will be hashed before storing. Due to the size limit, arrays or string type data are not given as indexed parameters in order to avoid them from being part of the topics
array.
To explain the topics
array better, let’s look at an example. Suppose a Bored Ape NFT with token ID: 8009
was transferred from address 0xdafce4acc2703a24f29d1321adaadf5768f54642
to the address 0xdbfd76af2157dc15ee4e57f3f942bb45ba84af24
. The topics
array of the generated Transfer
event log then will look like this:
To get the token ID of a particular Bored Ape NFT, you can use NFT marketplaces like OpenSea.
Why you see more than 32 characters
If you are confused as to why the elements in thetopics
array have 64 characters (omit 0x
) instead of 32, know that they are in hex format and 2 hex digits make up one byte.indexed
keyword) are added to the data
section of the log. Unlike topics
, the data
section cannot be queried. In the case of our Transfer
event, since all the parameters are indexed, the data
section will be empty and it will simply contain 0x
as a placeholder.
All the other fields, like the contract address, block number, and transaction hash contain the respective information. As a whole, the log for the Transfer
event will look something like this:
Ways to capture the logs
Get an Ethereum node
One of the most crucial components that we need in order to capture the event logs is access to an Ethereum node. To set up your own node:- Head over to Chainstack and set up an account.
- Once you have your account, deploy an Ethereum mainnet node in Chainstack.
Using curl to capture event logs
To get the event logs using curl, we need to invoke theeth_getLogs
RPC method. This method retrieves the logs that match certain filter criteria defined by the user. The filter criteria can include things like the address of the contract emitting the logs, the topics of the logs, and the range of blocks to search. Here is the sample curl request for fetching the Transfer
event logs from the BAYC contract:
params
of the request, we have provided the fromBlock
and toBlock
fields. They are used to represent the range of blocks to search. While providing the range, keep in mind that the larger the range, the higher the number of logs and thus the higher the response size.
Recommended block range cap
Even though there are no limitations set in stone, in order to avoid node resource wastage and bulky response payloads, it is recommended to cap the block range at 8,000 for Chainstack Ethereum nodes.latest
to refer to the latest block. You can also use pending
and earliest
for referring to transactions that are not yet mined.
The address
field in the request parameter helps filter the events based on the contract (represented using the address) from which it was emitted. Since a contract can emit multiple events, we can use the topics
field to provide additional filtering. Here, within the topics
array, we have provided the signature of the Transfer
event—topics[0]
. This will get us the logs of only the Transfer
event from the BAYC contract.
Using JavaScript to fetch the logs
The web3.js library provides a far more convenient way to fetch the event logs. To do this, we make use of thesubscribe()
function. The function helps us subscribe to incoming logs, which we can filter and process using different options. The following script help subscribe to the Transfer
event logs from the BAYC contract:
fromBlock
,toBlock
). In the script, while providing the block number, you can directly use the decimal representation of the number.
Here, since we are expecting a stream of data, we are using the WSS endpoint of our Chainstack node. Just like our curl request, we have provided the contract address and the event signature within the topics
field as a means to filter the events.
To provide more filtering, you can specify more topics in the topics
array. To demonstrate this, here is a script that subscribes to the Transfer
event log of a particular BAYC NFT based on its token ID:
topics
array. This script will only fetch the Transfer
event logs that were generated while transferring that particular NFT. When providing topics to the topics
array, we can use the null
value to represent all the topics whose values we didn’t specify (the from
and to
address in this case). Also, before adding the token ID to the array, we used the padLeft()
function to add zeros to the hex value, in order to cover the 32-byte size specification.
And with that, we have covered all the major ways of fetching Ethereum event logs.