Skip to content

OverviewHow Lane works

Overview

How Lane works

A swap goes through five stages. The first one runs continuously in the background. The other four run inside a single GET /v1/quote request, typically in under a second.

flowchart LR
  A[1. Index<br/>every block] --> B[2. Route<br/>in memory]
  B --> C[3. Quote<br/>1 eth_call]
  C --> D[4. Build<br/>calldata]
  D --> E[5. Simulate<br/>eth_estimateGas]
  E --> F[tx + approval<br/>returned to the caller]

1. Index (continuous)

The indexer keeps a copy of every pool's state in memory.

  • Discovery. At start-up it backfills PoolCreated (V3), PairCreated (V2) and v4 Initialize events from the configured factories and PoolManagers, plus the Solidly, Slipstream and Algebra creation events. Any other contract emitting those events is checked too: a V3 factory must expose the standard interface, and a V2 factory's swap fee is read from its pair bytecode. Unknown fee formulas are rejected rather than guessed.
  • State. It reads slot0/liquidity (V3), getReserves (V2) or extsload (V4) in large multicalls. For V3 it also loads the initialized ticks around the current price (±16 bitmap words) so the estimator can simulate tick crossings.
  • Updates. Each block it reads the Swap, Sync, Mint, Burn and v4 ModifyLiquidity logs. Most state comes straight from the log, with no extra call. A WebSocket RPC (WS_URL) triggers a sync as soon as a block lands; otherwise it polls every POLL_MS.
  • Reorgs. It remembers the block hash at each tip. On a mismatch it walks back to the common ancestor, re-reads the pools touched since, and replays.
  • Cold start. Quoting is available as soon as V2/V3 pools are loaded (~20–40 s). The rest loads in the background, one step at a time (token metadata first, then other factories, v4 pools, which can take ~15 min, and V3 ticks); a failing step is retried a minute later without blocking the others.
  • Snapshots. State is saved atomically to api/data/snapshot.json (or Redis) every 60 s, so a restart replays only the missed blocks (~4 s instead of a full backfill).

Details: Routing engine → Indexer.

2. Route (in memory, no RPC)

The pathfinder runs a depth-first search over the live pool graph:

  • Up to 3 swaps, mixing V2, V3 and V4 pools.
  • Plus one free rewrap between allow-listed WHYPEs (unwrap one, wrap the other, 1:1). This connects pools that use different WHYPE contracts, e.g. PERPME → WHYPE_A ⇄ WHYPE_B → USDC.
  • Native HYPE is tried through every WHYPE.
  • The last hop only considers pools that contain tokenOut, and enumeration is capped at 5,000 paths.

Each path gets an estimate: exact for V2 (x·y=k), tick-by-tick for V3, constant-liquidity for V4. The estimate is net of the gas the route costs. The top 20 go to the next stage.

3. Quote (one eth_call)

The 20 candidates are quoted exactly by Executor.quote(Split[]), all in a single Multicall3 eth_call. quote runs the real swaps against real pool state; the swap callback reverts with the amounts (the QuoterV2 technique), so no balance or approval is needed and tick crossings, hooks and dynamic fees are all accounted for.

Then the router tries splitting: it takes the best route plus up to 2 routes that share no pool with it (and use the same WHYPE), quotes each at 5%, 10%, … 95% of the amount (one more multicall), and searches every allocation in 5% steps (231 allocations for 3 legs). The split is kept only if it beats the best single route after the gas of the extra legs, and the final split is re-quoted exactly.

Example on testnet, HYPE→USDC: 0.01 HYPE uses one route; 1 HYPE splits 70/30; 20 HYPE splits 60/40.

4. Build

The API encodes Executor.swap(tokenIn, tokenOut, amountIn, minOut, recipient, wrappedNative, deadline, splits):

  • minOut = amountOut × (1 − slippageBps / 10,000), with amountOut already net of the router fee and of measured token taxes.
  • deadline = now + 300 s.
  • With permit2=true it builds swapWithPermit2(...) and returns the EIP-712 data to sign instead. See Permit2.

If a pre-graduation launchpad token pays more on its bonding curve than through the aggregator, the API builds a call to the launchpad's router instead (venue: "launchpad").

5. Simulate

Before anything is returned:

  1. Balance and allowance of sender are read. If the allowance is short, approval holds the approve transaction to send first.
  2. The exact transaction is passed to eth_estimateGas from sender. A revert (balance, hook, blocked transfer, anything) makes simulation.ok = false and adds a warning to tx.

The caller gets tx, approval, simulation, and the full route breakdown. See Build and execute.

On-chain execution

When the user sends tx:

  1. The Executor pulls tokenIn (or wraps the HYPE sent as value).
  2. For each split leg, it runs its hops. V2: transfer to the pair, compute the output from reserves and the amount actually received, call swap. V3: call pool.swap; the pool calls back uniswapV3SwapCallback and the Executor pays exactly what is owed. V4: unlock → swap → settle/take inside unlockCallback. Rewrap: withdraw then deposit.
  3. It measures what it actually received at every step (so fee-on-transfer tokens work), takes feeBps on the total output, forwards it, and sends the rest to recipient.
  4. It checks that the recipient's balance grew by at least minOut. If not, everything reverts.

Next: Architecture

lane.markets · Make the most of every move.