Understand the Listener Contract
A listener is a special contract Sim IDX executes onchain. It has handler functions which are called when certain conditions are triggered onchain (e.g., when another contract calls a function, or a contract with a matching ABI emits an event). The Listener contract itself emits events which Sim IDX stores in your app’s database.Mental Model
- A transaction is executed onchain.
- Sim IDX checks whether it matches any trigger you defined during deployment.
- When there’s a match, Sim IDX invokes the corresponding handler in your listener contract.
- The handler emits one or more events that capture the facts you care about.
- Sim IDX stores each event as a new row in the appropriate table of your app database.
File Anatomy
Before diving into listener development, make sure you understand the overall app folder structure and how the
listeners/ folder fits into your Sim IDX app.listeners/src/.
| Contract | Purpose | Location |
|---|---|---|
Triggers | Registers all triggers via addTrigger. Must be named Triggers and defined in Main.sol. | listeners/src/Main.sol |
| Listener(s) | One or more contracts that implement handler logic and emit events. They can have any name and be defined in any .sol file within listeners/src/. | listeners/src/ |
Main.sol file from the sample app down step-by-step.
Imports
Simidx.sol provides core helpers, while Generated.sol contains the Solidity code created from your ABIs.
The ./UniswapV3FactoryListener.sol import brings in the listener contract from a separate file.
Triggers Contract
This contract tells Sim IDX when to run your code using a trigger, which specifies a target contract and the handler to call. TheTriggers contract must be named Triggers and must be located in listeners/src/Main.sol.
You can also use helpers like
chainAbi and chainGlobal.
For other trigger types, see the Listener Features page.Main.sol
BaseTriggers: An abstract contract fromSimidx.solthat provides theaddTriggerhelper.triggers(): The required function where you register all your triggers.new UniswapV3FactoryListener(): The listener contract is instantiated from its own contract type.chainContract(...): This helper function uses theChainsenum for readability. The sample app registers the same trigger for Ethereum, Base, and Unichain, demonstrating how to monitor a contract across multiple networks.
Listener Contract
This is where you implement your business logic. The sample app places this logic inlisteners/src/UniswapV3FactoryListener.sol.
UniswapV3FactoryListener.sol
- Inheritance: The listener extends an abstract contract (
UniswapV3Factory$OnCreatePoolFunction) that is automatically generated from your ABI. This provides the required handler function signature and typed structs forinputsandoutputs. - Events: Emitting an event like
PoolCreateddefines the shape of your database.
UniswapV3FactoryListener.
You should use descriptive names for your contracts.
For larger projects, you can even split logic into multiple listener contracts, each in its own .sol file within the src/ directory.
Access Transaction Data with Handler Contexts
In the example above, you’ll notice theonCreatePoolFunction handler receives a FunctionContext memory ctx parameter. Every handler in Sim IDX receives a context object, which provides rich metadata about the top-level transaction and the specific call that triggered your code. For example, you can get the address of the contract that was called (callee) and pass it to your event:
Handler Contexts Reference
For a complete guide to all available properties and common usage patterns, see the full Handler Contexts reference.
Define and Emit Events
Events are the bridge between your listener’s logic and your database. When your listener emits an event, Sim IDX creates a database record.From Events to DB
The framework automatically maps your event to a database view. The event name is converted tosnake_case to become the view name, and each event parameter becomes a column.
For example, the PoolCreated event from the sample app results in a queryable pool_created view:
| chain_id | caller | pool | token0 | token1 | fee |
|---|---|---|---|---|---|
| 1 | 0x1f98431c8ad98523631ae4a59f267346ea31f984 | 0xf2c1e03841e06127db207fda0c3819ed9f788903 | 0x4a074a606ccc467c513933fa0b48cf37033cac1f | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 | 10000 |
Extending an Event
To capture more data, you simply add parameters to your event definition and update theemit statement in your handler. Let’s modify the sample app to also record the block number.
1. Extend the Event Definition
Add the new blockNumber parameter to your PoolCreated event in Main.sol.
onCreatePoolFunction handler.
pool_created table will automatically include the new block_number column.
Prefer
blockNumber() over Solidity’s block.number to standardize access across all supported blockchains.Trigger Onchain Activity
Sim IDX can trigger on contract events as well as function calls, both before and after they execute. This allows you to capture a wide range of onchain activity. To add a new trigger to your listener, you’ll follow a simple, five-step process:- Discover the Trigger: Find the abstract contract for your target function or event in the generated files.
- Extend the Listener: Add the abstract contract to your listener’s inheritance list.
- Define a New Event: Create a Solidity event to define your database schema.
- Implement the Handler: Write the function required by the abstract contract to process the data and emit your event.
- Register the Trigger: Call
addTriggerin yourTriggerscontract to activate the trigger.
UniswapV3FactoryListener contract.
We will extend the Listener to also index the OwnerChanged event from the Uniswap V3 Factory.
1. Discover the Trigger
Look insidelisteners/lib/sim-idx-generated/UniswapV3Factory.sol. You will find an abstract contract for the OwnerChanged event.
2. Extend the Listener
AddUniswapV3Factory$OnOwnerChangedEvent to the inheritance list of the UniswapV3FactoryListener contract in UniswapV3FactoryListener.sol.
UniswapV3FactoryListener.sol
3. Define a New Event
Inside theUniswapV3FactoryListener contract, add a new event to define the schema for the owner_changed table.
4. Implement the Handler
Implement theonOwnerChangedEvent function required by the abstract contract, also inside UniswapV3FactoryListener.
5. Register the Trigger
Finally, add a new trigger for this handler in yourTriggers contract within Main.sol. You will need to instantiate the listener first.
Main.sol
Function Triggers
The framework supports both post-execution and pre-execution function triggers. Post-Execution: This is what the sample app uses withonCreatePoolFunction. The handler is called after the contract’s function completes, so it has access to both inputs and outputs.
Pre-Execution: To react to a function before it executes, you use the corresponding Pre- abstract contract (e.g., preCreatePoolFunction). The handler receives a PreFunctionContext and only has access to the function’s inputs, as outputs have not yet been generated. This logic can live in its own file.
UniswapV3FactoryPreExecutionListener.sol
Triggers contract inside Main.sol.
Main.sol
Test Your Listener
Sim IDX gives you two ways to make sure your listener behaves as expected while you build.Unit Tests with Foundry
Thelisteners folder is a Foundry project. sim test is a thin wrapper around forge test. It will compile your contracts, execute all Forge unit tests inside listeners/test/, and surface any failures.
Historical Replay
Usesim listeners evaluate to see how your listener reacts to real onchain data before pushing your updates. This command compiles your listener and executes the transactions in any block range you specify.
Next Steps
You’ve now seen how to create triggers, emit events, and validate your listener. Here are a few great ways to level-up your Sim IDX app.Listener Features
Explore more listener features like triggering by ABI, global triggers, interfaces, and DB indexes.
Deployment Guide
Push your app to a staging or production environment and watch it process live onchain activity.
API Development Guide
Build fast, type-safe endpoints that surface the data your listener captures.