The Swap page trades any token for any other through the Levee Router, a contract that chains up to three swaps across Levee pools, public Uniswap v4 pools and Pons launches in one transaction. This chapter covers what the Router does on chain, how the app finds and ranks routes, and the checks that run before you sign. The pools themselves are described in Levee pools.
A route is a list of hops
You hand the Router the hops, the input amount, your minimum output, a recipient and a deadline. Each hop names a venue kind and carries that venue's own data:
| Kind | Venue | data | Direction flag |
|---|---|---|---|
0 | any Uniswap v4 pool | abi.encode(PoolKey, zeroForOne, hookData) | zeroForOne: currency0 goes in |
1 | a DLMM pair | abi.encode(pair, swapForY) | swapForY: token X goes in |
2 | a Pons bonding curve | abi.encode(curve, isBuy) | isBuy: the pair token goes in |
3 | WETH | empty | wraps ETH or unwraps WETH; not a swap hop |
The Router checks the shape first. routeTokens requires each hop to start in the token the previous one ended in, treats ETH and WETH as the same asset, and allows at most MAX_SWAP_HOPS (3) hops that are not WETH hops. Then it takes your input: either ETH sent as value, which must equal amountIn and must suit the first hop, or an ERC-20 pulled with transferFrom. Between hops it wraps or unwraps as the next venue needs. To receive ETH when the last pool pays WETH, the route ends with a WETH hop.
Each v4 hop must consume its whole input or the call reverts PartialFill; hops carry no price limit of their own. A Pons hop works only before the token graduates (PonsGraduated after). The only price protection in a swap is therefore your final minimum, checked once at the end. On success the Router sends the output to the recipient and emits Swapped, with the zero address standing for ETH.
Quoting a route
quoteExactIn(hops, amountIn) runs the same hops without moving tokens. It is not a view function, so the app calls it with an eth_call. A v4 hop runs the real swap inside a PoolManager unlock and reverts with QuoteResult(amountOut); a DLMM hop asks the pair's getSwapOut and reverts InsufficientLiquidity if the bins cannot absorb the whole input; a Pons hop prices the curve from its reserves and fees, launch snipe tax included. A quote reads the chain at that moment. Anything that moves the price before your transaction lands is covered by your minimum, nothing else.
How the app picks a route
The Swap page, the trade terminal and Pons token pages share one search (lib/amm/route.ts and useSwapRoutes):
- 01Gather the poolsLevee's DAMM, DLMM and stock pools from the API, the v4 ones with their exact key from the deployment manifest. The Pons venue of either token, from
getLaunchedTokenon the Pons factory: the curve before graduation, the v4 pool keyed with the factory'smemeHookafter. With a Router deployed, also public hookless v4 pools at the 0.01 %, 0.05 %, 0.3 % and 1 % tiers that hold in-range liquidity. Hookless only, because a hook can run arbitrary code during a swap. - 02Build candidatesEvery pool holding both tokens, and every two-hop route through USDG, ETH or a Pons pair token, up to 40. The app does not build three-hop routes. It drops a route whose first pool wants native ETH when you pay WETH, because the Router cannot unwrap your input, and adds a final WETH hop where your output needs it. For the same reason WETH to ETH on its own never goes through the Router: the page unwraps it with WETH's own
withdraw, 1:1 and with no approval. ETH to WETH is a one-hop Router route. - 03Quote and rankEach candidate is quoted every 15 s. A failed or zero quote drops the route; the rest are ranked by output. The best route through a Levee DLMM pair moves to the top unless it pays 15 % or more below the leader (
DLMM_FALLBACK), since that far down the bins the price impact gate would refuse it anyway. - 04Measure the impactPrice impact is the leading route's rate against a quote for 1/1000 of the amount on the same hops, so the pool fees cancel out.
With no Router in the manifest the page runs in demo mode: it estimates each route from the pools' own prices net of their fees, labels the result as an estimate and will not send it.
Before you sign
The minimum you sign is the leading quote less your slippage: minAmountOut = out × (1 − slippage). The page shows the rate, the price impact, the pools' compound fee, that minimum and how many routes returned a quote, with up to four other routes and how far each trails the best. Two gates can hold the button:
- Slippage. Presets of 0.1 %, 0.5 %, 1 % and 3 %, 0.5 % by default, or any custom value up to 50 %. Above 5 % you must acknowledge the risk first.
- Price impact. Flagged from 1 %. From 5 % you tick a box accepting the impact; from 15 % the swap is refused and you are asked to split the trade. An impact the page cannot measure does not gate.
The transaction carries a 20-minute deadline. For an ERC-20 the steps are an exact-amount approval to the Router, then the swap; the Permit2 step is shown as done because the Router never uses it. The page URL follows the form (?in=, ?out=, ?amt=), so a swap can be shared as a link.
Reading prices
A v4 pool's own price is token1 per token0, in address order, and a DLMM pair's is Y per X. The NVDA stock pool sorts USDG first, so its own price is about 0.004 NVDA per USDG. Every page turns a pool round to read base in quote: the quote is USDG when the pool has it, otherwise ETH or WETH, otherwise token1, and a stock pool's asset is always the base. The hop flags above follow the raw order, never the displayed one.
Buy and Sell in the trade terminal
The terminal's spot Buy and Sell normally trade the asset's Uniswap pool through the Universal Router: approve, Permit2, swap, with a 10-minute deadline. Its quote is an estimate at the pool's on-chain price less the pool fee and Levee's 25 bps fee on the output.
The same route search runs beside it. When a live Router quote pays more than that estimate, the trade goes through the Levee Router instead: one exact approval, no Permit2, no 25 bps Levee fee (the pools' own fees still apply), and the route is drawn above the button. The Uniswap estimate ignores price impact, so the Router takes over only when it beats a generous number. The terminal applies the slippage acknowledgement and checks the pool's on-chain price against the API, but not the Swap page's impact gates; for a large trade, use the Swap page.
Reference
| Name | Allowed | Default | Effect |
|---|---|---|---|
Slippage | 0 - 50 % | 0.5 % | Becomes minAmountOut. Above 5 % needs an acknowledgement. |
IMPACT_WARN | fixed | 1 % | Impact shown as a warning. |
IMPACT_CONFIRM | fixed | 5 % | You must accept the impact to swap. |
IMPACT_BLOCK | fixed | 15 % | The swap is refused. |
DLMM_FALLBACK | fixed | 15 % | How far below the best a DLMM route may pay and still lead. |
Deadline | fixed | 20 min (Swap), 10 min (terminal) | Passed to swapExactIn; Expired after it. |
| Router error | When |
|---|---|
Expired, ZeroRecipient, ZeroAmount | past the deadline, no recipient, or a zero input |
BadRoute, BadKind | an empty route, more than 3 swap hops, hops that do not chain, or an unknown kind |
ValueMismatch | ETH sent that differs from amountIn or does not suit the first hop, or no ETH for a route that starts in ETH |
PartialFill | a v4 hop that could not take its whole input |
InsufficientLiquidity | a DLMM hop in a quote that the bins cannot fill |
PonsGraduated | a curve hop after the token graduated |
TooLittleReceived | the final output is below minAmountOut |
contracts/src/venue/periphery/Router.solswapExactIn, quoteExactIn, routeTokens, the ETH and WETH bridgecontracts/src/venue/interfaces/IRouter.solhop kinds, their data, errors and the Swapped eventcontracts/src/venue/periphery/PonsAdapter.solcurve hops: swap, quote from reserves, graduation checkapps/web/lib/amm/route.tshop encoding, candidates, dlmmFirst, estimates, price impactapps/web/lib/amm/venues.tswhere legs come from: Levee pools, public v4 pools, Ponsapps/web/lib/amm/useSwapRoutes.tsthe live quote loop behind every pageapps/web/lib/amm/orient.tsbase and quote for displayapps/web/app/(app)/swap/SwapClient.tsxslippage, the impact gates, the approval and the swapapps/web/lib/amm/useLeveeSpot.tswhen the terminal hands a trade to the Router