Contracts
The Router and Executor, their addresses on every chain, the swap interface, the fee model and the security properties the contracts enforce.
Tirio's on-chain side is two contracts. The Router is the fixed entry point that holds user allowances, takes the fee and enforces the minimum output. The Executor interprets the packed route the API built off chain and knows nothing about any DEX. Venue logic lives in the API; adding a protocol means a new route pattern off chain, never a Router change.
Addresses
| Contract | Address | Role |
|---|---|---|
| Router | 0x0000000000F315f7C21DcdF4885FB01064f58da5 | Entry point, allowance holder, fee vault, owner admin |
| Executor | 0x863625023bBaAA6AF51746f6C4117220357aA149 | Route interpreter, callable only by the Router, replaceable by the owner |
| Create3Factory | 0x02944AFeAD4d9c7A09c6D9E56B9027B84B6F242F | CREATE3 deployer that makes the two addresses above identical on every chain |
The addresses are the same on every chain: the factory sits at the same CREATE2 address everywhere (Arachnid deployer, salt 0), and a CREATE3 address depends only on the factory, the deployer and the salt, not on the contract's code.
| Chain | Status |
|---|---|
| BNB Chain (56) | Live since 2026-09-28. Fee 1 bps. |
| Robinhood Chain (4663) | Not deployed yet. |
Router
struct SwapParams {
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 minOut;
address recipient;
address inputTo;
uint256 deadline;
}
function swap(SwapParams calldata p, bytes calldata route) external payable returns (uint256 amountOut);
function swapForwardingNativeOut(SwapParams calldata p, bytes calldata route) external payable returns (uint256 amountOut);- Fee.
amountIn × feeBps / 10000is taken from the input. The default is 1 bps, the hard cap 100 bps, and the owner may set it anywhere from 0 to the cap. The rest of the input is the Executor's budget. The owner can mark specific payer addresses fee-exempt. - ERC-20 input. The Router uses the payer's allowance. With
inputToset, the net amount is transferred straight to that address (a pool that can be paid directly). Otherwise the budget is recorded in transient storage and the Executor pulls it withpull(token, to, amount), which works only fortokenIn, only up to the budget and only during that swap. - Native input.
msg.valuemust equalamountIn; the net amount is forwarded to the Executor. - Output. Paid directly to
recipient.minOutis checked against the recipient's balance change; a balance that drops counts as zero. Less thanminOutreverts withInsufficientOutput. - Deadline.
block.timestamp > deadlinereverts withExpired. swapForwardingNativeOut. For recipients that forward incoming native coin (for example exchange deposit addresses). The route pays the Router, the Router measures its own balance increase and forwards exactly that to the recipient. The API selects it automatically when a contract recipient receives the native coin.- A transient reentrancy lock guards both entry points. There is no swap event.
- Owner functions.
setOwner(zero rejected),setExecutor,setFeeBps,setFeeExempt, andwithdraw(token, to)for accumulated fees (for ERC-20 fees one unit of dust stays behind).
Executor
execute(bytes route) runs a packed list of operations and may be called only by the Router. Each operation is a target, calldata, a list of patches that write run-time amounts into that calldata, an optional native value and an optional capture of the return data into a transient register. Amounts come from a constant, the Executor's current balance of a token, a register, the calldata of the callback being served, or the constant-product output formula of a Uniswap V2 style pair, optionally scaled by a share in parts per million.
A target can be armed for a callback: when it calls back into the Executor (Uniswap V3 payment, V4 and Infinity unlock, Balancer V3, Maverick, iZi), the Executor runs the sub-route found in the callback's arguments. This is how every payment and settlement pattern is expressed without any DEX-specific code on chain.
The Executor keeps no allowances and no balance between transactions; every swap ends with its balance at zero. DEX fees are never hard-coded: the API reads the current fee from each pool, and concentrated-liquidity pools apply their fee themselves.
Security properties
- The Router can move only the payer's
tokenIn, only up to the swap's budget and only inside a swap the payer sent. There is no other path to a user's allowance. - The output must reach the recipient:
minOutis enforced on the recipient's actual balance change, not on a number the route reports. - Administrative functions are
onlyOwnerand limited to the fee (at most 100 bps), fee exemptions, the Executor address, the owner address and withdrawing accumulated fees. - The Router is not upgradeable: no proxy, no delegatecall, no way to change its code. Its address is fixed on every chain.
- The Executor is replaceable by the owner through
setExecutor, which is how new protocols that need new route primitives are added. A replaced Executor still runs only inside swaps users signed, can pull at most that swap's budget oftokenIn, and the Router still enforces the user'sminOutand deadline. - Every swap is atomic: it settles completely or reverts completely.
Testing
The contracts are covered by fuzz and invariant suites, 58 mainnet-fork tests against real pools on four chains, full line and function coverage of the contract sources and static analysis. The contracts have not yet been audited by a third party.