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.
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;
}
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);
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.
Responses are generated using AI and may contain mistakes.