# Solidity Events and NatSpec Documentation: A Practical Guide

> How to write Solidity events and NatSpec documentation correctly — why they matter for audits, indexing, and developer experience, with practical examples.

- **Published:** 2026-06-23
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** solidity, events, natspec, documentation, smart-contracts
- **Canonical URL:** https://clixo.sh/blog/solidity-events-and-natspec-documentation-guide

Two things get skipped in most Solidity codebases that should never be optional: events and NatSpec documentation. Both are lightweight to write and expensive to omit. Missing events make off-chain systems brittle and incident response slow. Missing NatSpec makes audits more expensive and integrations harder. Neither is a nice-to-have.

## Why Solidity Events Matter

```mermaid
sequenceDiagram
  participant SC as Smart Contract
  participant TL as Transaction Log
  participant IDX as Indexer
  participant FE as Frontend
  participant MON as Monitoring

  SC->>TL: emit Transfer(from, to, amount)
  TL->>IDX: log stored on-chain
  IDX->>FE: query events via subgraph
  IDX->>MON: alert on role change event
  FE->>FE: update UI balance
```

Events write data to the transaction log — a separate, cheaper storage layer outside of contract state. Indexed off-chain by services like The Graph, Etherscan, and custom indexers, events are how the outside world knows what happened inside your contract.

Without events, external systems must either read state repeatedly (expensive and unreliable) or cannot track contract activity at all. With events, any observer can reconstruct the full history of your contract's state changes from the logs.

The gas comparison makes the decision obvious: writing a new value to a cold storage slot costs around 20,000 gas. Emitting an event costs roughly 375 base gas plus per-byte topic and data costs. For state that is only consumed off-chain, events are the correct mechanism.

## How to Write Solidity Events Correctly

Declare events at the top of the contract, before state variables:

```
event Transfer(
    address indexed from,
    address indexed to,
    uint256 amount
);

event OwnershipTransferred(
    address indexed previousOwner,
    address indexed newOwner
);
```

Emit them in the function body after the state update:

```
function transfer(address to, uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient balance");
    balances[msg.sender] -= amount;
    balances[to] += amount;
    emit Transfer(msg.sender, to, amount);  // after state update
}
```

### When to Use `indexed`

The `indexed` keyword allows the event parameter to be used as a filter in log queries. You can have up to three `indexed` parameters per event. Indexed parameters are stored as topics (32 bytes each in the log structure) rather than as ABI-encoded data.

Index parameters that subscribers will filter on: addresses, token IDs, status codes. Do not index parameters that are only informational or that are already derivable from context.

For `string` and `bytes` types, indexing stores a `keccak256` hash of the value rather than the value itself. This means you can check for equality but cannot decode the original string from the log.

### What Events to Emit

Every meaningful state change should emit an event. This includes:

- Token transfers and approvals
- Ownership or role changes
- Configuration updates (fee changes, parameter updates)
- User-facing actions (deposits, withdrawals, claims)
- Pause or unpause events
- Contract upgrades (in upgradeable systems)

If a state change could affect a user's balance, permissions, or expectations — emit an event. Do not emit events for internal computations that have no external significance.

## Solidity NatSpec Documentation

NatSpec (Natural Language Specification) is Solidity's inline documentation format. Block explorers like Etherscan use it to display human-readable descriptions to users before they sign transactions. Auditors use it to understand intent quickly. Frontend developers use it to generate accurate UI copy.

A well-documented contract is faster and cheaper to audit — auditors spend less time inferring intent from code.

### NatSpec Tags

```
/// @title A simple token vault
/// @author YourTeam
/// @notice This contract holds tokens on behalf of users and allows withdrawal after a lock period
/// @dev Uses the Checks-Effects-Interactions pattern throughout; no external calls before state updates
contract TokenVault {

    /// @notice Deposit tokens into the vault
    /// @dev Caller must approve this contract before calling
    /// @param amount The number of tokens to deposit (in the token's native decimals)
    function deposit(uint256 amount) external {
        // ...
    }

    /// @notice Withdraw tokens after the lock period expires
    /// @param amount The number of tokens to withdraw
    /// @return success True if the withdrawal succeeded
    function withdraw(uint256 amount) external returns (bool success) {
        // ...
    }

    /// @notice Emitted when a user deposits tokens
    /// @param user The depositing address
    /// @param amount The amount deposited
    event Deposited(address indexed user, uint256 amount);
}
```

The core tags:

- `@title` — contract-level, one-line description
- `@notice` — human-readable explanation for end users (shown by Etherscan)
- `@dev` — technical notes for developers and auditors
- `@param` — description of each function parameter
- `@return` — description of each return value
- `@inheritdoc` — inherit documentation from a parent interface

### Where NatSpec Matters Most

**Etherscan and wallet UIs.** When users are about to sign a transaction, `@notice` from the called function is displayed in some wallets and on verified contracts. Writing `@notice` as if a non-technical user will read it is the correct approach.

**Audit readiness.** Auditors who have clear intent documentation can focus on verifying that the code matches the intent, rather than spending time inferring what the intent was. Clear NatSpec directly reduces audit hours and cost.

**Developer integrations.** Other developers integrating with your contract benefit from accurate parameter descriptions and return value documentation. This reduces integration bugs.

### A Practical NatSpec Standard

Apply these rules to every contract you ship:

- Every `public` and `external` function has `@notice` and `@param` for each parameter
- Every function with a non-trivial return value has `@return`
- Every event has `@notice` with the trigger condition and `@param` for each field
- Every state variable that is not obvious from its name and type has a `/// @dev` or `/// @notice` comment
- The contract itself has `@title` and `@notice`

This takes roughly 10-15 minutes per contract and pays back multiples in audit efficiency.

## Generating Documentation from NatSpec

Foundry can generate documentation from NatSpec automatically:

```
forge doc
```

This produces a documentation site in `docs/` from your NatSpec annotations. Running this during development keeps documentation in sync with code and surfaces functions that are missing annotations.

Events and NatSpec are not polish — they are engineering discipline. If you want a contract system delivered with complete event coverage and NatSpec that reflects actual intent, [Clixo](https://clixo.sh/services/smart-contract-development) builds Solidity systems where documentation is a deliverable, not an afterthought.

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
