The 1inch aggregator demonstrated a simple idea: if you're looking for the best price on only one DEX, you're leaving money on the table. Since then, order routing systems have evolved into complex solutions with split routing, multi-hop, and specialized algorithms. We design and implement such systems turnkey—from scratch or as an extension of existing infrastructure.
A custom order routing system is needed when standard aggregators (1inch, ParaSwap, 0x) don't support the required chain; integration of custom protocols is needed; control over liquidity sources is required; or existing APIs are too slow for a trading bot. Our track record: 5+ years in blockchain development and 30+ DeFi projects.
How Does an Order Routing System Across Multiple DEXes Work?
Routing is a pathfinding problem in a weighted directed graph. Vertices are tokens. Edges are pools—each pool creates two directed edges: A→B and B→A with the price in that direction.
To find the best path for a fixed amountIn, the task is to find the path with the maximum product of exchange rates (or equivalently, the minimum sum of negative logarithms). This is a modification of the Bellman-Ford or Dijkstra algorithm.
But there is a nuance that makes the problem harder: the price in a pool depends on volume. For amountIn = 100 USDC, the best route might be a Uniswap V3 0.05% pool. For amountIn = 1,000,000 USDC, the same pool gives 3% slippage, while splitting across multiple pools yields 0.3%. This transforms the problem from pathfinding in a fixed-weight graph into an optimization problem with volume-dependent weights.
How Does Split Routing Reduce Slippage on Large Orders?
For large orders, the optimal solution is not a single route but distributing volume across multiple paths. The approach using binary search for optimal split for two routes:
function findOptimalSplit(
routeA: Route,
routeB: Route,
totalAmount: bigint,
steps: number = 20
): { splitA: bigint; splitB: bigint; totalOut: bigint } {
let bestSplit = { splitA: 0n, splitB: totalAmount, totalOut: 0n }
for (let i = 0; i <= steps; i++) {
const fraction = i / steps
const amountA = BigInt(Math.floor(Number(totalAmount) * fraction))
const amountB = totalAmount - amountA
const outA = amountA > 0n ? simulateRoute(routeA, amountA) : 0n
const outB = amountB > 0n ? simulateRoute(routeB, amountB) : 0n
const totalOut = outA + outB
if (totalOut > bestSplit.totalOut) {
bestSplit = { splitA: amountA, splitB: amountB, totalOut }
}
}
return bestSplit
}
For N routes, the problem becomes N-dimensional optimization—we apply gradient descent or Nelder-Mead with constraints (sum of shares = 1, all shares ≥ 0).
Pool Simulation: Accuracy vs Speed
Uniswap V2: Exact Formula
function getAmountOutV2(amountIn: bigint, reserveIn: bigint, reserveOut: bigint): bigint {
const amountInWithFee = amountIn * 997n
const numerator = amountInWithFee * reserveOut
const denominator = reserveIn * 1000n + amountInWithFee
return numerator / denominator
}
Uniswap V2 Whitepaper
Uniswap V3: Tick Traversal
V3 requires iterating over the tick bitmap to find the nearest active ticks. Full simulation is accurate but slow—several milliseconds for a large swap with traversal through many ticks.
For quick estimation (during route screening), we use an approximation via current sqrtPriceX96 and liquidity without tick traversal—accurate for small volumes, with error for large ones. Exact simulation is run only for final candidates.
Curve StableSwap: Iterative Formula
Curve uses the invariant A * n^n * sum(x_i) + D = A * D * n^n + D^(n+1) / (n^n * prod(x_i)). Computing amountOut is iterative (Newton's method). For JavaScript/TypeScript—BigInt arithmetic with 18-decimal precision.
Balancer WeightedPool
Balancer with weighted pools (e.g., 80/20 BAL/ETH) uses a different invariant. getAmountOut depends on token weights in the pool—a more complex formula than V2.
On-Chain vs Off-Chain Routing
Routing can happen entirely on-chain (smart contract finds the route inside the transaction) or off-chain (computation off-chain, result passed to the contract).
On-chain routing: full transparency, no possibility of manipulation by the aggregator. Problem: limited gas, cannot iterate over all routes. Used for simple cases (2–3 pools maximum).
Off-chain routing (approach of 1inch, ParaSwap): computation in the backend, the contract receives a ready-made route. The contract only executes. Gas efficient, route can be more complex. Risk: the backend might return a suboptimal route. Protection via slippage protection: minAmountOut in the transaction guarantees a minimum for the user.
How Is the Router Contract Structured?
The contract must support heterogeneous routes: part through Uniswap V2, part through V3, part through Curve.
struct SwapStep {
address pool;
address tokenIn;
address tokenOut;
uint24 fee; // For V3
uint8 dexType; // 0=V2, 1=V3, 2=Curve, 3=Balancer
bytes extraData; // Additional parameters per DEX type
}
function multiSwap(
SwapStep[] calldata steps,
uint256 amountIn,
uint256 minAmountOut,
address recipient
) external returns (uint256 amountOut) {
IERC20(steps[0].tokenIn).transferFrom(msg.sender, address(this), amountIn);
uint256 currentAmount = amountIn;
for (uint256 i = 0; i < steps.length; i++) {
currentAmount = _executeStep(steps[i], currentAmount);
}
require(currentAmount >= minAmountOut, "Slippage exceeded");
IERC20(steps[steps.length-1].tokenOut).transfer(recipient, currentAmount);
return currentAmount;
}
_executeStep dispatches to the specific DEX implementation based on dexType. Each implementation is a separate library (Solidity library pattern) to save bytecode size.
Why Is Pool Cache Critical for Speed?
For fast routing without RPC calls per request, we need a cache of the current state of pools: WebSocket subscriptions on Sync events (V2 pools) and Swap events (V3 pools) via eth_subscribe("logs"). On each event, we update reserves/sqrtPrice in memory.
For 500–1000 active pools, that's ~50–100 events/block on Ethereum mainnet. Processing via event-driven architecture (Node.js EventEmitter or Rust tokio channel) with ≤1ms update latency.
Cold start: on service startup, we need to load the current state of all pools via multicall. For 1000 pools—5–10 multicall transactions (up to 200 calls each), takes 1–3 seconds.
Architectural Approach Comparison
| Approach | When It Fits | Complexity | Latency |
|---|---|---|---|
| Simple multi-hop | 3–5 chains, top-5 DEXes | Low | 200–500ms |
| Split routing | Large orders ($50K+) | Medium | 500ms–1s |
| With pool cache | Trading bot, < 50ms | High | 10–50ms |
| On-chain router | Maximum transparency | Medium | 1 block |
Estimated Development Time
| Stage | Time |
|---|---|
| Analytics (list of DEXes, latency requirements) | 1–2 days |
| Routing engine development (graph, algorithm, simulation) | 5–7 days |
| Router contract (multi-step, fork tests) | 3–5 days |
| Pool cache (WebSocket + in-memory) | 3–5 days |
| Integration, testing, documentation | 2–3 days |
Step-by-Step Development Process
- Pool and liquidity graph analysis — compile a map of tokens and pools for your chains.
- Pathfinding algorithm development — implement a modified Dijkstra accounting for volume dependency.
- Simulation integration — write simulators for Uniswap V2/V3, Curve, Balancer.
- Mainnet fork testing — run orders of various sizes, compare against baseline.
- Deployment and monitoring — launch on testnet and mainnet, configure alerts.
What's Included in the Work
- Architecture documentation: pool graph description, caching scheme, contract specification.
- Routing engine: pathfinding and split routing implementation with V2/V3/Curve/Balancer support.
- Router contract: multi-step execution with slippage protection.
- Pool cache (optional): WebSocket subscriptions + in-memory store.
- Testing: Foundry fork tests, Echidna fuzzing.
- Integration: deployment on testnet and mainnet, monitoring setup.
- Team training: internal documentation, code review.
Order a consultation—we'll send a technical and commercial proposal within 1 day after the brief. Get a detailed analysis of your current infrastructure and routing optimization recommendations. Contact us to discuss your project.







