
Solidity Events: Emitting Logs for Transparent Smart Contracts
What Solidity events are
An event is a typed log declaration in a smart contract. When the contract executes emit, the EVM writes a log entry to the transaction receipt. That log is not part of contract state, so it does not consume storage in the same way as variables.
A basic event looks like this:
event Transfer(address indexed from, address indexed to, uint256 amount);And it is emitted like this:
emit Transfer(msg.sender, recipient, amount);The key idea is simple: events are for recording what happened, not for storing data that the contract must read later.
Why events matter in real projects
Events are useful whenever something important happens and external systems need to know about it.
Common examples include:
- token transfers
- deposits and withdrawals
- role changes
- order creation and fulfillment
- governance votes
- NFT minting and metadata updates
Without events, frontends would need to poll contract state constantly and infer what changed. That is inefficient and often unreliable. With events, applications can subscribe to logs and react to changes immediately after a transaction is mined.
Events vs storage
| Feature | Events | Storage |
|---|---|---|
| On-chain read access | No | Yes |
| Gas cost | Lower than storage writes | Higher |
| Query by external systems | Easy | Requires RPC calls or indexing |
| Permanent transaction history | Yes | Only current state |
| Suitable for application state | No | Yes |
Use events for history and notifications. Use storage for data the contract must enforce or reuse.
Declaring and emitting events
A Solidity event declaration defines the data shape of the log. The syntax is straightforward:
event Deposit(address indexed account, uint256 amount, uint256 balanceAfter);You emit it with the emit keyword:
emit Deposit(msg.sender, msg.value, address(this).balance);Example: simple vault contract
The following contract records deposits and withdrawals while emitting events for each action.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Vault {
mapping(address => uint256) private balances;
event Deposited(address indexed account, uint256 amount, uint256 newBalance);
event Withdrawn(address indexed account, uint256 amount, uint256 newBalance);
function deposit() external payable {
require(msg.value > 0, "No ether sent");
balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value, balances[msg.sender]);
}
function withdraw(uint256 amount) external {
require(amount > 0, "Amount must be positive");
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawn(msg.sender, amount, balances[msg.sender]);
}
function balanceOf(address account) external view returns (uint256) {
return balances[account];
}
}This contract uses storage for balances and events for observability. A frontend can listen for Deposited and Withdrawn to update the UI without reading every account balance after each transaction.
Indexed parameters and log filtering
Solidity lets you mark up to three event parameters as indexed. Indexed values are stored in the log topics, which makes them searchable by off-chain tools.
event OrderCreated(uint256 indexed orderId, address indexed buyer, uint256 total);Why indexing matters
Indexing is especially useful when you need to filter logs by:
- user address
- token ID
- order ID
- proposal ID
For example, if a marketplace emits OrderCreated with buyer indexed, an indexer can efficiently fetch all orders for a specific wallet.
What to index
A good rule is to index values that are commonly used as filters.
| Parameter type | Index it? | Reason |
|---|---|---|
| User address | Usually yes | Common query key |
| Token ID | Usually yes | Useful for NFT and asset tracking |
| Amount | Usually no | Rarely used as a filter |
| Status string | Usually no | Strings are not ideal for indexed filtering |
Remember that indexed parameters are not stored in the same way as regular event data. They are optimized for searching, not for full-text retrieval.
Event data and topic limits
An event can have:
- up to three indexed parameters
- any number of non-indexed parameters
- one log entry per
emit
Non-indexed parameters are stored in the event data section and are still available to off-chain consumers, but they are not as easily filterable as indexed values.
Design guideline
If a value is important for searching, index it. If it is important for display or auditing but not filtering, keep it non-indexed.
For example:
event ProposalSubmitted(
uint256 indexed proposalId,
address indexed proposer,
string title,
string description
);Here, proposalId and proposer are good filters. title and description are useful for user interfaces, but indexing them would not help much.
Events are not readable by contracts
A common mistake is to treat events like on-chain state. Contracts cannot read past logs from other contracts during execution. Events are only available to external observers after the transaction is mined.
That means you should never rely on an event to enforce business logic.
Incorrect approach
Do not emit an event and expect another contract to “see” it and act on it automatically. Smart contracts cannot subscribe to logs the way off-chain services can.
Correct approach
If a contract needs to know something, store it in state or pass it directly in function arguments. Use events only to mirror or announce what happened.
Practical patterns for event design
Good event design makes your contract easier to integrate and debug.
1. Emit after state changes
Emit events after the contract updates its storage and before the function exits successfully.
balances[msg.sender] += msg.value;
emit Deposited(msg.sender, msg.value, balances[msg.sender]);This ensures the event reflects the final state of the transaction.
2. Include enough context
Events should be self-describing. A log that only says ActionOccurred(uint256 id) may be too vague for analytics or UI rendering.
Prefer:
event RoleGranted(address indexed account, bytes32 indexed role, address indexed sender);This tells you who received the role, which role was granted, and who performed the action.
3. Keep payloads compact
Events are cheaper than storage, but they are not free. Avoid emitting large blobs of data unless necessary. For example, storing a full JSON document in an event is usually a bad idea.
If a large payload is required, consider emitting a hash or a content identifier instead.
event DocumentRegistered(bytes32 indexed documentHash, string uri);4. Use consistent naming
Choose past-tense names for events because they describe something that already happened:
DepositedWithdrawnOrderFilledOwnershipTransferred
This naming style improves readability in logs and code reviews.
Events in token and NFT contracts
Events are central to token standards. ERC-20 and ERC-721 rely heavily on them so wallets and explorers can track asset movements.
ERC-20 example
The standard Transfer and Approval events are essential for token tracking.
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);Wallets and block explorers use these logs to display balances, allowances, and transfer history.
NFT example
For NFTs, events often capture minting and metadata changes.
event Minted(address indexed to, uint256 indexed tokenId);
event MetadataUpdated(uint256 indexed tokenId, string tokenURI);A marketplace or indexer can use these events to build a searchable catalog of assets.
Common mistakes to avoid
Emitting too many events
Every event adds log data to the transaction receipt. Emitting redundant logs increases gas usage and can clutter off-chain processing.
Avoid patterns like emitting multiple events for the same state transition unless each event serves a distinct integration need.
Using events as a database
Events are not a replacement for storage. If your contract needs to calculate balances, ownership, or permissions, that information must live in state variables.
Forgetting to index useful fields
If your frontend needs to query by user or asset ID, make those fields indexed from the start. Retrofitting indexing later can be inconvenient for downstream systems.
Emitting before validation
Do not emit an event before all checks pass. If the transaction reverts, the log is discarded anyway, but emitting early makes the code harder to reason about.
Logging sensitive data
Events are public. Never emit secrets, private keys, or sensitive business data. Even if a value is not indexed, it is still visible on-chain.
How frontends and indexers use events
Most dApps consume events through libraries or indexing services.
Typical workflows include:
- a wallet shows transaction history from
Transferlogs - a frontend listens for
Depositedand updates balances - a backend service indexes
OrderCreatedandOrderFilled - analytics dashboards aggregate
VoteCastorProposalExecuted
For production applications, many teams use an indexer to transform raw logs into queryable application data. This is especially important when you need pagination, historical search, or cross-contract reporting.
Best practice for integration
Design events with consumers in mind. Ask:
- What will the frontend need to display?
- What will the indexer need to filter?
- What values are useful for audit trails?
- Which fields are stable enough to be part of the public log schema?
Treat event signatures as part of your contract’s external API.
Testing event emission
When testing Solidity contracts, verify that important actions emit the expected events with the right arguments.
A good test should confirm:
- the event is emitted
- the indexed fields are correct
- the payload matches the final state
- no unexpected extra events are emitted
This is especially important for token transfers, governance actions, and marketplace operations, where off-chain systems depend on logs.
If you change an event signature in a deployed contract, downstream consumers may break. Tests help catch accidental changes before deployment.
Event design checklist
Before shipping a contract, review each event:
- Does it represent a meaningful state transition?
- Are the right fields indexed?
- Is the payload compact but informative?
- Is the event emitted after state changes?
- Is the name clear and past-tense?
- Would an indexer or wallet be able to use it easily?
If the answer to most of these is yes, the event is probably well designed.
Conclusion
Solidity events are a foundational part of smart contract development. They provide a low-cost, structured way to expose contract activity to wallets, dashboards, indexers, and analytics tools. Used well, they make your application easier to integrate, easier to debug, and much more transparent.
The key is to treat events as an external communication layer: emit them for important actions, index the fields people will search on, and keep contract logic independent from log consumption.
