Sim IDX listeners may occasionally hit Solidity compilation errors during development. This page explains the most common issues and shows how to resolve them.

Handle Name Conflicts

When working with multiple ABIs, you may encounter functions or events with the same name, which can cause compilation errors. Sim IDX provides two solutions.

1. Multiple Listeners

The recommended approach is to split your logic into separate, dedicated listener contracts for each ABI. This keeps your code clean and modular.

// In Triggers.triggers()
Listener1 listener1 = new Listener1();
Listener2 listener2 = new Listener2();

addTrigger(..., listener1.triggerOnSwapFunction());
addTrigger(..., listener2.triggerOnSwapFunction());

// Separate listener contracts
contract Listener1 is ABI1$OnSwapFunction { /* ... */ }
contract Listener2 is ABI2$OnSwapFunction { /* ... */ }

2. Prefixed Naming for Shared State

If you need to share state between handlers for conflicting functions within a single contract, you can configure sim.toml to prefix the generated names.

Set codegen_naming_convention = "abi_prefix" in your sim.toml file.

This changes the generated function names, allowing you to implement them both in the same contract:

Example Listener with Shared State
contract CombinedListener is ABI1$OnSwapFunction, ABI2$OnSwapFunction {
    // Store every recipient that swaps via DEX #1
    address[] public swapRecipients;

    // Emit an alert for large swaps coming through DEX #2
    event LargeSwap(address indexed dex, address indexed recipient, uint256 amountOut);

    // Handler for ABI1 (e.g., Uniswap V2 style router)
    function ABI1$onSwapFunction(
        FunctionContext memory /*ctx*/,
        ABI1$SwapFunctionInputs memory inputs
    )
        external
        override
    {
        // Track who received tokens in this swap
        swapRecipients.push(inputs.to);
    }

    // Handler for ABI2 (e.g., SushiSwap router)
    function ABI2$onSwapFunction(
        FunctionContext memory /*ctx*/,
        ABI2$SwapFunctionInputs memory inputs
    )
        external
        override
    {
        // Fire an event if the swap paid out at least 1 ETH worth of tokens
        if (inputs.amountOut >= 1 ether) {
            emit LargeSwap(msg.sender, inputs.to, inputs.amountOut);
        }
    }
}

contract Triggers is BaseTriggers {
    function triggers() external override {
        CombinedListener listener = new CombinedListener();

        // DEX #1 (ABI1) on Ethereum
        addTrigger(
            chainContract(Chains.Ethereum, 0xAbCDEFabcdefABCdefABcdefaBCDEFabcdefAB),
            listener.ABI1$triggerOnSwapFunction()
        );

        // DEX #2 (ABI2) on Ethereum
        addTrigger(
            chainContract(Chains.Ethereum, 0x1234561234561234561234561234561234561234),
            listener.ABI2$triggerOnSwapFunction()
        );
    }
}

To learn more about the codegen_naming_convention property and other sim.toml configuration options, visit the App Structure page.

Stack Too Deep Errors

You may encounter a Stack too deep compilation error if your event contains more than 16 parameters, or if your handler function declares too many local variables. This is due to a fundamental limit in the Solidity EVM.

The solution is to use a pattern called Struct Flattening. You group your event parameters into a struct and then define your event to take this struct as a single, unnamed parameter. Sim IDX recognizes this specific pattern and will automatically “flatten” the struct’s members into individual columns in your database. This gives you the best of both worlds: code that compiles and a clean, relational database schema.

1

Define a Struct

Create a struct containing all the fields you want in your database table.

struct EmitSwapData {
    uint64 chainId;
    bytes32 txnHash;
    uint64 blockNumber;
    uint64 blockTimestamp;
    bytes32 poolId;
    address fromToken;
    uint256 fromTokenAmt;
    string fromTokenSymbol;
    string fromTokenName;
    uint64 fromTokenDecimals;
    address toToken;
    uint256 toTokenAmt;
    string toTokenSymbol;
    string toTokenName;
    uint64 toTokenDecimals;
    address txnOriginator;
    address recipient;
    address poolManager;
}
2

Update Event Definition

Change your event to accept the struct as a single, unnamed parameter. This is the crucial step that enables struct flattening.

// Incorrect: event Swap(EmitSwapData emitData);
// Correct:
event Swap(EmitSwapData);
3

Populate and Emit the Struct

In your handler, create an instance of the struct, populate its fields, and emit it.

function onSwapFunction(...) external override {
    // ...
    EmitSwapData memory emitData;
    emitData.chainId = uint64(block.chainid);
    emitData.txnHash = ctx.txn.hash;
    // ... populate all other fields

    emit Swap(emitData);
}

By following this pattern, you can define events with any number of parameters while keeping your code compliant with the EVM’s limitations.