DOCS / 04

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

ContractAddressRole
Router0x0000000000F315f7C21DcdF4885FB01064f58da5Entry point, allowance holder, fee vault, owner admin
Executor0x863625023bBaAA6AF51746f6C4117220357aA149Route interpreter, callable only by the Router, replaceable by the owner
Create3Factory0x02944AFeAD4d9c7A09c6D9E56B9027B84B6F242FCREATE3 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.

ChainStatus
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 / 10000 is 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 inputTo set, 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 with pull(token, to, amount), which works only for tokenIn, only up to the budget and only during that swap.
  • Native input. msg.value must equal amountIn; the net amount is forwarded to the Executor.
  • Output. Paid directly to recipient. minOut is checked against the recipient's balance change; a balance that drops counts as zero. Less than minOut reverts with InsufficientOutput.
  • Deadline. block.timestamp > deadline reverts with Expired.
  • 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, and withdraw(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: minOut is enforced on the recipient's actual balance change, not on a number the route reports.
  • Administrative functions are onlyOwner and 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 of tokenIn, and the Router still enforces the user's minOut and 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.