Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to your sixth lesson in the "Advanced Solidity & Secure Development" module.
Introduction
In our last lesson, we delved into on-chain security and optimization by mastering arithmetic overflow/underflow protection and the strategic use of unchecked blocks. We focused on making the internal logic of our contracts both safe and efficient.
Today, we shift our focus from internal contract logic to how contracts communicate with the outside world. A smart contract that can't effectively report its state changes is like a server without an API—its utility is severely limited. This is where Solidity events come in. For a front-end developer, understanding events is the key to building responsive, real-time decentralized applications.
Learning Outcome: By the end of this lesson, you will be able to use events with indexed parameters for efficient off-chain data querying.
We'll cover what events are, why they are a more gas-efficient logging mechanism than storage, and most importantly, how the indexed keyword is the critical feature that enables powerful and efficient data filtering for off-chain applications.
1. What Are Events and Why Are They Essential?
At a high level, events are a logging facility built into the EVM. They allow smart contracts to "emit" information about specific actions that have occurred (e.g., a token was transferred, an ownership change, a new trade was executed). This information is stored in a special, cheaper data location called the transaction logs, which is separate from contract storage.
The crucial trade-off is that this log data is not accessible to other smart contracts, but it is readily available to off-chain clients. This makes events the primary mechanism for:
- dApp Front-ends: To listen for state changes and update the UI without needing to constantly re-fetch all the data from the contract.
- Data Indexers: Services like The Graph listen to events to build a structured, queryable database of blockchain activity.
- Notifications: To alert users about important on-chain actions related to their account.
Let's start with a video that provides a great overview of what events are and the problem they solve.
All about Logging, Events, Solidity, Bloom Filter | Brownie
This video by Patrick Collins explains the fundamental purpose of events in Solidity. It highlights their role as a gas-efficient logging mechanism and their importance for off-chain services.
Please watch the first 3 minutes and 14 seconds (00:00 - 03:14). Focus on the distinction between storing data in logs versus contract storage and why this is crucial for off-chain infrastructure.
To reinforce this, it's helpful to understand why simply inspecting past transactions isn't a viable alternative for querying historical data.
Solidity Events | By RareSkills
This article from RareSkills, 'Solidity Events', clearly explains why events are superior to inspecting transaction history for data retrieval.
Read the sections 'Solidity Events' and 'Retrieving transactions faster'. Pay attention to the limitations of RPC methods like getTransaction and how events provide a dedicated, efficient mechanism for querying contract-specific history.
2. The Anatomy of an Event: indexed Parameters (Topics)
Now for the most critical part of this lesson: making events searchable. By default, the data you emit in an event is just a blob of ABI-encoded bytes. To find a specific event, an off-chain service would have to pull all events from your contract and decode each one—a highly inefficient process.
To solve this, Solidity provides the indexed keyword. When you mark an event parameter as indexed, its value is placed in a special, searchable data structure within the logs called topics.
Here’s the breakdown:
- Non-
indexedparameters: Are ABI-encoded together into thedataportion of the log. They are cheap to store but are not searchable at the node level. indexedparameters (Topics): Are stored separately as up to three 32-byte "topics". Off-chain clients can ask an Ethereum node to return only the logs that match specific topic values.
Think of it like a database: indexed parameters are like indexed columns you can use in a WHERE clause for efficient filtering. Non-indexed parameters are the rest of the data in the row, which you only retrieve after you've found the rows you're interested in.
The event's signature hash is also always included as the first topic (topic 0), allowing you to filter by event name. This leaves you with up to three indexed parameters per event.
Let's see how this is defined and used.
All about Logging, Events, Solidity, Bloom Filter | Brownie
Continuing with the Patrick Collins video, this next segment details the syntax for declaring events and explains the crucial difference between indexed and non-indexed parameters.
Watch from 03:14 to 04:44 and from 05:32 to 06:42. Focus on the explanation of 'topics' and how Etherscan visually separates the 'Topics' (indexed) from the 'Data' (non-indexed).
The official Solidity documentation provides the canonical definition.
Contracts — Solidity 0.8.32-develop documentation
The official Solidity documentation offers a concise and authoritative explanation of events and the indexed attribute.
Read the section 'Events'. It's a short but dense section. Pay close attention to the paragraph that begins 'You can add the attribute indexed to up to three parameters...'.
Here is a visual from a block explorer that shows exactly how these parts are displayed.

3. Implementation: Emitting and Querying
Now let's put it all together.
Emitting Events in Solidity
You declare an event at the contract level and then use the emit keyword inside a function to trigger it.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract UserRegistry {
// Event Declaration
// 'user' and 'role' are indexed for efficient searching.
// 'timestamp' is not indexed.
event UserAdded(
address indexed user,
bytes32 indexed role,
uint256 timestamp
);
mapping(address => bytes32) public userRoles;
function addUser(address _user, bytes32 _role) public {
require(userRoles[_user] == 0, "User already exists");
userRoles[_user] = _role;
// Emitting the event
emit UserAdded(_user, _role, block.timestamp);
}
}
Querying Events Off-Chain
Your front-end application would use a library like ethers.js or web3.js to listen for these events. The key is that you can create a filter based on the indexed parameters.
For the contract above, you could easily query for all users who were assigned the ADMIN role, or all actions performed by a specific user address, without having to sift through every UserAdded event ever emitted.
This article provides excellent, practical examples of how to do this with modern JavaScript libraries.
Solidity Events | By RareSkills
This section of the RareSkills article provides concrete code examples for listening to and filtering events using ethers.js and web3.js.
Read the sections 'Listening to Events' and 'Solidity indexed events vs non-indexed events'. Focus on the ethers.js code snippets. Notice how tokenContract.filters.Approval(filterAddress, null, null) creates a filter for an indexed parameter, and how null acts as a wildcard.
4. Best Practices and Gas Considerations
Choosing what to index is a key design decision. It's a trade-off between searchability and gas cost.
- Index what you need to filter by: The most common candidates for indexing are addresses (
msg.sender,owner), IDs (tokenId), and status enums/bytes32roles. - Don't index large or continuous data: Values like token transfer amounts, strings, or timestamps are poor candidates for indexing. It's inefficient to filter for an exact
uint256amount, and you can't perform range queries (e.g., "all transfers > 100 ETH") at the node level anyway. You would fetch the events based on an indexed parameter (like the sender) and then filter by amount on the client side. - Be mindful of gas: Each indexed parameter adds to the gas cost of the transaction.
The RareSkills article provides a clear breakdown of the gas costs.
Solidity Events | By RareSkills
Finally, let's look at the gas implications and best practices for indexing.
Read the sections 'Solidity events best practices' and 'Gas cost to emit Solidity event'. The gas formula clearly shows that the number of topics is a primary driver of the cost.
Conclusion
You have now learned the standard mechanism for communicating from the blockchain to the outside world. Mastering events, and particularly the strategic use of indexed parameters, is fundamental to building performant and responsive dApps. It bridges the gap between on-chain logic and off-chain user experience.
Key Takeaways:
- Events are for Off-Chain Consumers: They provide a cheap, efficient way to log state changes for front-ends and other services, but are inaccessible to other smart contracts.
indexedEnables Filtering: Marking an event parameter asindexedturns it into a "topic," allowing off-chain clients to efficiently query for events matching that parameter's value.- Topics vs. Data: Indexed parameters go into searchable
topics; non-indexed parameters go into a non-searchabledatablob. - Strategic Indexing: Index parameters that you anticipate needing to filter by (like addresses or IDs), but avoid indexing large or continuous data to save gas. You can have a maximum of three indexed parameters per event.
Next Steps:
This lesson concludes the "Advanced Solidity & Secure Development" module. You've covered reentrancy, access control, arithmetic safety, and now, efficient off-chain communication.
In our next lesson, we will begin the "Fungible Tokens & DeFi Primitives" module. We will start by putting today's knowledge to immediate use as we implement an ERC-20 token, which relies heavily on the Transfer and Approval events to function within the broader Ethereum ecosystem.