Listener Contract
The core of a Sim IDX application is the listener, a Solidity contract that defines what onchain data to index. By writing simple handlers for specific contract function calls or events, you instruct the Sim IDX framework on which data to capture and store in your database.
This guide covers the structure of a listener contract, how to add indexing for new functions, and how to test your logic.
App Structure Explained
Primary Development Folders
The abis/
and listeners/
folders are the two folders you will work with most often.
ABI Files
The abis/
folder contains JSON ABI files of smart contracts you want to index.
The sample app includes abis/UniswapV3Factory.json
for the Uniswap V3 Factory contract.
If you want to add an additional ABI and learn what happens when you run the command, see the sim abi add
CLI command docs.
Understanding the Listener Contract
A listener is a special contract Sim IDX simulates 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 store 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
Main.sol
lives in listeners/src
and contains two contracts:
Contract | Purpose |
---|---|
Triggers | Registers triggers via addTrigger . No business logic. |
Listener | Implements handlers and emits events that define your database schema. |
Let’s break the file down step-by-step.
Imports
These two imports pull in everything provided by the Sim IDX framework.
Simidx.sol
provides the enums, structs, abstract contracts and helper functions you will use while writing your indexing logic.
Generated.sol
contains the Solidity code created by the sim abi add
command.
For the sample app this file includes the generated Solidity bindings for the Uniswap V3 Factory located at listeners/lib/sim-idx-generated/UniswapV3Factory.sol
.
Triggers contract
A contract that tells Sim IDX when to run your code. Sim IDX does this through what we call a trigger. A trigger tells Sim IDX when to call one of your handlers for a function or event on an external contract.
BaseTriggers
comes from Simidx.sol
. It is an abstract contract that provides helper functions, most importantly addTrigger
.
BaseTriggers
requires you to implement the triggers
function. Inside this function you register every trigger your application needs.
Here you create an instance of Listener
, the second contract in Main.sol
. We will look at this contract in the next section.
addTrigger
takes two arguments. The first argument identifies the on chain target by chain identifier and contract address. The second argument is the handler selector that the listener contract exposes.
Let’s take a look at the Listener contract now.
Listener contract
Listener
extends UniswapV3Factory$OnCreatePoolFunction
, an abstract contract generated automatically by sim abi add
and made available through Generated.sol
.
After declaring the contract you define the events that you want to emit.
Emitting an event defines the shape of the database that Sim IDX creates. The event name becomes the table name and each parameter becomes a column.
Next you implement the handler that the generated abstract contract expects.
Because you extended UniswapV3Factory$OnCreatePoolFunction
you must implement onCreatePoolFunction
.
Each handler name follows the pattern of the function or event it processes. When this handler runs Sim IDX gives you three arguments: a transaction context, the decoded inputs and the decoded outputs. You can use this data to emit one or more events which Sim IDX writes to your application database.
From Events to DB
When the handler emits PoolCreated
, Sim IDX creates a database view of the event’s name in snake_case.
For example, PoolCreated
becomes pool_created
.
Here’s an example of the contents:
chainId | caller | pool | token0 | token1 | fee |
---|---|---|---|---|---|
1 | 0x1f98431c8ad98523631ae4a59f267346ea31f984 | 0xf2c1e03841e06127db207fda0c3819ed9f788903 | 0x4a074a606ccc467c513933fa0b48cf37033cac1f | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 | 10000 |
If you add or remove parameters on the event and deploy your changes, Sim IDX automatically adjusts the table.
Capturing more event data
You can define and emit as many events as you like—each with whatever properties you care about—and you can update an event definition at any time.
In the example below we modify the Listener
contract in listeners/src/Main.sol
to capture one additional piece of information: the block number in which the pool was created.
Extend the event
Adding a parameter changes the shape of the table that Sim IDX creates for this event.
blockNumber
will appear as a new column after you push your changes.
Emit the new data
Once you’ve saved these changes you can test your listener to check against real data and verify that everything works as expected.
Hooking into more functions and events
When you’d like to index additional calls from the same contract, Sim IDX makes it easy. Each ABI you add is converted into a Solidity file inside listeners/lib/sim-idx-generated/
. That file contains every helper function, struct, and abstract contract you’ll need. All of these are exported through Generated.sol
, which is imported at the top of listeners/src/Main.sol
. It brings every generated hook into scope for your Listener
contract.
The workflow for tapping into an extra function or event is:
- Discover the hook: open the generated file (e.g.
UniswapV3Factory.sol
) and locate the abstract contract that corresponds to the function or event you care about. - Extend
Listener
with that abstract contract. - Add a new event that matches the data you want in your database.
- Implement the handler to emit the event.
- Register the trigger inside the
Triggers
contract.
Below we walk through adding support for the owner()
function on the Uniswap V3 Factory contract.
1. Discover the hook
In listeners/lib/sim-idx-generated/UniswapV3Factory.sol
you will find:
This abstract contract represents the owner()
function. Implementing its handler lets your listener react whenever that function is executed. The paired triggerOnOwnerFunction()
helper returns the selector you’ll reference in the Triggers
contract.
2. Extend the listener
By inheriting from the new abstract contract you promise the compiler that you’ll provide a concrete onOwnerFunction
implementation, unlocking typed access to the decoded outputs.
3. Define an event
Events define your database schema. Here we record the queried owner plus a bit of useful context about the call.
4. Implement the handler
Everything you need like the execution context and the decoded outputs is provided by the generated bindings.
Because the abstract contract’s method is external
, remember to mark this implementation with override
.
5. Register the trigger
Triggers.triggers()
is your trigger registry.
Adding this additional addTrigger
call tells Sim IDX to invoke your new handler whenever owner()
is called on the Uniswap V3 factory contract.
6. Test it out
Save your changes, then test your listener to replay historical data and confirm that OwnerChanged
rows are appearing.
This replays block 12369662 on Ethereum Mainnet so you can confirm that OwnerQueried
rows appear in the console.
Adding another ABI
- Place the ABI JSON in
abis/
(e.g.abis/MyContract.json
). - Run:
The command regenerates listeners/lib/sim-idx-generated/
, re-creates Generated.sol
, and autogenerates all the functions, structs, and abstract contracts you need to work with the ABI you just added.
After running the command you can open listeners/src/Main.sol
and extend Listener
with any of the freshly-generated *OnSomeEvent
/ *OnSomeFunction
contracts. Browsing listeners/lib/sim-idx-generated/
is the quickest way to see every event and function that is now available to you.
Testing your listener
Because listeners/
is a Foundry project you can write standard Forge tests in listeners/test/
. Running
will compile the listener, execute the tests, and surface any failures in the CLI just like in any other Solidity project.
You can also run
to execute your listener against historical blockchain data before deploying it. See the CLI docs for more details.
Next steps
From here you can broaden the coverage of your application by adding new ABIs and triggers or start exposing the indexed data through custom APIs. Once you’re happy with your listener you can deploy the app following the deployment guide, or head over to the API development guide to build rich endpoints that power your applications.