Routing
The four stage quote engine, the exact split solve, and the guarantee that a routed quote is never worse than one path used alone.
The quote engine turns a set of pools read at one chain state into a route and a set of numbers a transaction can be held to. It runs in four stages, in order.
Stages one to three choose an allocation. Stage four prices it. Every figure the user is shown comes from stage four, which is integer arithmetic that matches the validators.
Stage 1: candidate path enumeration
Nodes are the registered stablecoins plus ADA. Edges are pools.
With that few nodes the enumeration of all simple paths up to routing.maxPathLength is
exhaustive rather than heuristic, so nothing is missed by a search cutoff. A path never
revisits an asset, so it cannot loop, and every intermediate asset must be in
routing.intermediateAssets. The user's own input and output are never intermediates.
Each path carries how it would settle, computed by pathAtomicity:
- A single pool takes its own venue's class.
- A multi hop path where every pool is Kernel is
atomic. - A multi hop path inside one Commitment venue that supports multi routing is
single-order, because it becomes one order with one end to end minimum. - Anything else is
needs-settlement.
Paths that would settle leg by leg are dropped unless the request passes allowNonAtomic.
Stage 2: reduction to effective pools
Every venue Zerochord routes across is constant product once you look at the right reserves. Minswap V2 is constant product on the reserves in its datum. Dano Finance's concentrated liquidity pool is constant product on its virtual reserves, and the concentrated part only decides what those virtual reserves are.
So a path of any length collapses to one triple of (reserveIn, reserveOut, fee), exactly.
For pool 1 then pool 2 with fee multipliers g1 and g2:
reserveIn = R1in * R2in / (R2in + g1 * R1out)
reserveOut = g1 * R1out * R2out / (R2in + g1 * R1out)
g = g1 * g2The reserves are carried as a rational with a shared denominator, and the fee multiplier as an exact rational. Flooring the composed reserves introduced a ranking error of about thirty basis points on small pools, which is enough to pick the wrong path.
Stage 3: exact split solve
Maximising total output over an allocation of one input across concave paths is a convex problem solved by equalising marginal outputs, which is water filling. Fixed per-order costs make it mixed integer, and at this size mixed integer means evaluating all subsets.
Each candidate carries a fixedCostOut: the cost of using that path at all, converted into
output base units. A venue that charges a non-refundable execution fee makes a small
allocation not worth taking, and the subset enumeration is what decides that.
Because every path reduced to a constant product triple in stage two, the water filling has a closed form and the whole solve is a bisection on one scalar per subset. That bisection is the only place a floating point number appears in the engine, and its answer is an allocation rather than a quoted output.
routing.maxCandidatePaths bounds how many candidates enter the enumeration.
Stage 4: integer requote
The winning allocation is walked hop by hop, calling each venue's own quote with that
venue's own arithmetic and rounding. That result is the quote.
Nothing from stage three survives into the numbers on screen. An off-by-one in rounding produces an order the validator rejects, not a slightly worse price, so the requote uses the same integers the validator will.
When a venue refuses the allocation
The reduction in stage two models a pool's curve but not its capacity. A concentrated liquidity pool stops filling once the price leaves its range, and the reduced form does not know that. So an allocation can be chosen that a venue then refuses to quote.
Dropping that branch would be wrong twice over: its share of the input would vanish, so the route would deliver less than the user paid for, and the comparison that chose the split would no longer describe the route being offered.
Instead the offending path is removed from the candidate set and the split is solved again. The whole input is always allocated to paths that can take it, and the engine refuses to return a quote that does not spend the whole input.
The floor: never worse than one path
Stages three and four can disagree. Composing a multi hop path floors its reserves, so the reduction can predict a few base units more than walking the pools really yields. Where the disagreement crosses the gap between two candidates, the solver can hand back a split that prices worse than one path used alone.
So the engine checks rather than trusting the two stages to agree. Every candidate the solver considered is requoted alone, at the full input, with the same integer arithmetic the quote is made of. If any of them beats the chosen route, the route becomes that single path.
The comparison is on the solver's own objective, output net of each path's fixed cost, not on gross output. Comparing gross would let the floor replace a route that pays one venue fee with one that pays three, on the strength of an output difference the extra fees more than consume.
The guarantee this buys, stated exactly: the quote is never worse than the best single candidate path the solver considered. It is not a claim about paths the pre-filter dropped.
Price impact
Price impact is the shortfall against the price a vanishingly small trade would get on the very same route, in hundredths of a basis point.
The reference is the best marginal rate among the paths the route actually uses, read straight off the exact reduction as a rational:
lim x->0 out(x)/x = reserveOut * gammaNumerator / (reserveIn * gammaDenominator)It is not measured by quoting a small probe amount. Probing was the first implementation and it was wrong: a probe of a ten thousandth of the branch floors to one base unit on a small trade, and one base unit through a pool floors its output to zero or one. The reported impact then either sat at zero or moved with the trade size in the wrong direction. The closed form has no probe, no floating point and no such regime.
The minimum output
minimumOut = amountOut - ceil(amountOut * slippageBps / 10 000)The deduction rounds up, so the on-chain bound is never looser than the quote implies.
slippageBps comes from the request, or from routing.defaultSlippageBps when the request
omits it.
Pools whose fee can move
A pool that can change its fee after the quote cannot honour a fill-at-the-quoted-rate
promise. routing.dynamicFeePolicy decides what happens:
excludedrops such pools from both the route and the baseline.labelkeeps them and relies on the risk flag reaching the interface.