// Developers

Integration

How to connect RouterX to an existing operator stack — liquidation, arbitrage, or other strategies — from contract addresses and approvals to executing a gas-aware route on-chain.

Architecture#

RouterX is composed of three layers. The Router contract is the single entrypoint you call to settle a trade. Behind it, Adapters normalize each DEX into a common swap interface, and the Quoter computes gas-adjusted paths across those adapters.

  • Router — validates the route, pulls input tokens, executes adapter calls, and enforces the minimum output.
  • Adapters — thin, audited wrappers for Uniswap, Curve, Balancer, PancakeSwap, and more.
  • Quoter — an off-chain quoting service and on-chain lens that both return identical, executable routes.

Supported chains#

ChainChain IDSwapCrossLimitDCA
Ethereum1
Avalanche43114
Arbitrum One42161
Optimism10
Base8453
BNB Chain56
Polygon PoS137
Robinhood Chain4663
HyperEVM999
World Chain480
Monad143
Katana747474
Unichain130
Plasma9745
Arc5042

Arc swap is ERC-20 only (no native wrap). Limit and DCA share the same chain set.

The router proxy is deployed at 0x1abe089620a6f3E3Ff802f2165AB8dbC10cd5CE8 on every chain via CREATE2, so you can hardcode a single address in cross-chain systems. Limit-order contracts use the same CREATE2 address on every chain. CrossComposer is CREATE2-identical on the 11 Composer source chains (Ethereum, Optimism, Unichain, Polygon, Base, Arbitrum, Avalanche, Monad, World Chain, HyperEVM, and Arc — not BSC, Robinhood, Katana, or Plasma).

Contract addresses#

The canonical deployment addresses. Router and limit-order contracts are identical on every chain via CREATE2. CrossComposer is CREATE2-identical on the 11 Composer source chains. Always verify against the on-chain source before approving tokens. Katana and Plasma have the router and limit-order protocol but not CrossComposer. Unichain and Arc have the router, limit orders, and CrossComposer; Arc Wave A is ERC-20 only (no native wrap).

ContractAddress
Router (UUPS proxy)0x1abe089620a6f3E3Ff802f2165AB8dbC10cd5CE8
Limit Order Protocol0x330d568D8b159dd80e8c625e194d70C70CA11113
Limit Order Bootstrap0xF4B6Bf4a747Ce68D42D8D828aD096E0DDB20000C
WETH Unwrapper0xEcC3717a8B374F0bcC03f6844C1236C052812622
CrossComposer0xe1426503902f201f922eDB45c7c2002D10502029

CrossComposer is live on 11 CCTP ∩ swap source chains (including Unichain and Arc). Cross quotes run in the SDK (rx.cross.quote); transfer history is served by https://cross-api.routerx.exchange (routerx-cross) via rx.cross.

Integration flow#

Every integration follows the same four steps:

  • Quote — request the best gas-adjusted route for your trade.
  • Approve — grant the Router an allowance for the input token (once per token, or use Permit2).
  • Simulate — confirm the route still executes at the target block.
  • Settle — submit the route calldata to the Router.

Approvals#

The Router pulls the input token via transferFrom, so it needs an ERC-20 allowance. You can either approve the token directly or use Permit2 to sign an off-chain approval and save a transaction.

approve.ts
import { erc20Abi, maxUint256 } from 'viem'

// One-time approval of the Router for WETH
await wallet.writeContract({
  address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
  abi: erc20Abi,
  functionName: 'approve',
  args: ['0x1abe089620a6f3E3Ff802f2165AB8dbC10cd5CE8', maxUint256],
})

Scope your allowances

For hot wallets running unattended, prefer per-trade Permit2 signatures over infinite approvals to limit exposure if a key is compromised.

On-chain settlement#

Settlement is a call to the CREATE2 router (0x1abe…5CE8) — typically swap, swapFromAVAX, or swapToAVAX with a Trade built from the quote offer. Prefer rx.swap.swap / buildSwapTx or GET /v1/:chain/swap so you do not hand-encode calldata.

RXRouter.sol
struct FormattedOffer {
    uint256[] amounts;
    address[] adapters;
    address[] path;
    uint256   gasEstimate;
    bytes[]   extras;      // aligned with adapters; pass to hops[i].extra
}

function findBestPathWithGas(
    uint256 amountIn,
    address tokenIn,
    address tokenOut,
    uint256 maxSteps,      // must be < 4
    uint256 gasPrice       // gwei, for gas-aware scoring
) external view returns (FormattedOffer memory);

struct Hop {
    address adapter;
    address tokenOut;
    bytes   extra;
}

struct SplitLeg {
    uint256 split;         // bps of post-fee amount; last leg takes remainder
    Hop[]   hops;
}

struct Trade {
    uint256    amountIn;
    uint256    amountOut;  // min out enforced on-chain
    address    tokenIn;
    SplitLeg[] legs;
}

function swap(Trade calldata trade, address to, uint256 fee) external;
function swapFromAVAX(Trade calldata trade, address to, uint256 fee) external payable;
function swapToAVAX(Trade calldata trade, address to, uint256 fee) external;
function swapWithPermit2(
    Trade calldata trade, address to, uint256 fee,
    uint256 nonce, uint256 deadline, bytes calldata signature
) external;
function swapToAVAXWithPermit2(
    Trade calldata trade, address to, uint256 fee,
    uint256 nonce, uint256 deadline, bytes calldata signature
) external;

function queryExactPath(
    uint256 amountIn,
    address[] calldata tokens,
    uint8[] calldata adapterIdx
) external view returns (FormattedOffer memory);

Zip a 1-leg quote into Trade by pairing each adapter with path[i + 1] and extras[i]. Multi-path trades set legs.length > 1; each split is a share of post-fee input out of 1e4, and the last leg receives the remainder.

swap.sol
FormattedOffer memory offer = router.findBestPathWithGas(
    amountIn, tokenIn, tokenOut, maxSteps, gasPrice
);

Hop[] memory hops = new Hop[](offer.adapters.length);
for (uint256 i; i < hops.length; ++i) {
    hops[i] = Hop({
        adapter:  offer.adapters[i],
        tokenOut: offer.path[i + 1],
        extra:    offer.extras[i]
    });
}

SplitLeg[] memory legs = new SplitLeg[](1);
legs[0] = SplitLeg({ split: 0, hops: hops });

router.swap(
    Trade({
        amountIn:  offer.amounts[0],
        amountOut: offer.amounts[offer.amounts.length - 1],
        tokenIn:   offer.path[0],
        legs:      legs
    }),
    recipient,
    feeBps
);

Native input uses swapFromAVAX with msg.value = amountIn. ERC-20 input needs an allowance to the router (or Permit2 via swapWithPermit2). For a caller-specified route that skips path search, use queryExactPath (tokens.length == adapterIdx.length + 1, hops in 1–4) then the same zip into Trade.

Production checklist#

  • Verify contract addresses against on-chain bytecode before approving.
  • Always pass a tight deadline (seconds, not minutes) for MEV-sensitive flows.
  • Simulate the route in the same block you intend to land in.
  • Set minAmountOut from your own model, not only from the quote.
  • Monitor the Adapter Registry for newly added or paused venues.

Ready to script it? Head to the SDK or read the raw API reference.