Building a Swapper That Quotes Faster Than the Market Moves

A quote is a forecast that has to survive to settlement. A small model of that gap, a map of the order-flow landscape, and the case that a router's real edge is the freshness of its state.

Mitchell Catoen, Principal Engineer
Two abstract graphs (purple and grey) connected by a dashed line on a grid, with text "the freshest graphc wins" and a smiling cartoon character.

    Every decentralized swap starts with a quote, and a quote is only an estimate. The number your wallet shows is computed from onchain state at one instant, and that state can move before your transaction lands. Almost everything interesting about trading infrastructure comes down to how each design handles the gap between when a quote is computed and when it settles. That gap is what separates plain AMM routing from RFQ, intents, and the proprietary AMMs that now dominate Solana flow.

    This is the problem my team works on inside Phantom's swapper. This post builds a small model of that gap, uses it to map the order-flow landscape, argues that the real bottleneck inside a router is the freshness of its liquidity graph, and backs the claim with a slippage study and a Dutch-auction experiment.

    A quote is a forecast

    When a user taps “swap token A for token B,” the amount the wallet shows is computed at some time from a snapshot of onchain state. The trade settles at a later time, against whatever state actually prevails at inclusion. In the gap the world moves: other people trade, arbitrageurs rebalance pools, fee markets shift. So the quote is a forecast, not a guarantee. If the forecast is off by more than the user’s tolerance, the transaction reverts. If the order sat in a public mempool, an adversary can manufacture the discrepancy on purpose (a sandwich). A routing engine, in other words, is “not [solving] a static optimization problem [but] a moving one,” because “chain state can change between quote time and execution time” (Cube).

    That gap is the thread running through everything below.

    A back-of-the-envelope model

    A little notation makes the rest of the post sharper. None of it is heavy.

    Liquidity as a graph

    Treat tradeable assets as vertices and each liquidity venue (an AMM pool, a centralized order-book market, a market-maker curve) as a directed edge between the assets it trades. You get a liquidity graph where the tokens are nodes, pools are weighted edges. This is the standard way the routing literature frames it (Deeplink Labs). Each edge carries a state (reserves, ticks/bins, fee parameters), and from that state you get a venue-specific output function out(x, state): the amount you receive for routing an input of size x through the venue at that state.

    Liquidity graph illustration: five Solana token icons as nodes connected by curved edges, each edge labeled with the venue it trades on (Raydium, Orca, or Meteora).

    For constant-product and concentrated-liquidity venues the output function is concave and non-decreasing over the relevant range; its slope is the venue’s instantaneous price, falling as x grows. That curvature is exactly what “price impact” means. The graph state, written state(t), is just every edge’s state at time t, and it is the object a router has to track, not any single quote. Build a route on stale state and you get a worse price, or a transaction that fails outright in simulation.

    Routing is a constrained allocation problem

    Take a trade of size x from a to b. A route is just a way of splitting x across paths from a to b, with the shares summing to one. Two levers: splitting across parallel paths, and hopping through intermediate tokens. Writing share_p for the fraction of x sent down path p, the router is solving:

    maximize    Σ (p ∈ paths)  out(share_p · x, state) − gas − fees
    subject to  Σ (p)  share_p = 1

    where gas is the gas/compute cost (more paths and longer paths cost more) and fees is the venues’ fees. Two things make this interesting:

    • Given a fixed set of candidate paths, picking the split is easy. Each path’s output is concave in its share, so maximizing the total is a concave problem you can solve exactly by water-filling: pour each marginal unit of input into whichever path has the best current marginal output, update state, repeat. It’s the same fill-path merging 0x does after sampling (0x).
    • Picking the set of paths is the hard part. Enumerating paths, choosing discrete venues, and paying the gas penalty make the joint problem too big to solve optimally in the time a quote can take (Deeplink Labs). Negative-weight cycles are arbitrage, so naive shortest-path (Dijkstra) doesn’t cut it; you need Bellman–Ford or heuristics (beam search, ML) once the graph is large. Recent papers formalize this token-graph routing and split-sizing problem (arXiv 2603.08337; arXiv 2508.03217). A convex formulation can go further and choose path topology, split proportions, and hop depths jointly, especially if it isn’t boxed in by per-transaction compute limits and can spill across multi-transaction bundles.

    Staleness, formally

    The optimization above runs on the state at quote time, but the trade settles against the state at settlement time. Call the quoted output q (what the quote-time state said you’d get) and the realized output r (what you actually got). Realized slippage is the log ratio:

    s = ln(r / q)

    That gap splits into two pieces. The first is market drift: the market itself repriced during the delay between quote and settlement, so any venue would now fill you differently. The second is a venue-specific execution error: even after accounting for the market move, the fill a single venue gives you lands a bit above or below what simulation predicted, because your picture of that one venue was slightly off at the moment of execution. Drift is systematic and grows with the delay; the error is idiosyncratic per venue and sets the floor you hit even at zero latency. The error is naturally a percentage, not a fixed amount (a fill that’s 0.1% worse is 0.1% worse at any size), so for a venue:

    realized = simulated × e^(error_j)

    where the error has a small, slightly adverse average per venue. The drift term is all about latency: the longer the delay between quote and settlement, the further the market can drift, with the drift’s variance growing in proportion to the delay. A revert happens when drift plus execution error blows through the user’s slippage tolerance.

    Two things fall out of this. First, slippage and price impact are not the same: price impact is the deterministic curvature you can read off the quote-time state, while slippage is the random execution variance driven by delay, volatility, and execution error. That’s why production systems estimate the tolerance from the empirical distribution of past slippage (for example, an 85th-percentile-of-history estimate with a safety buffer) instead of computing it from pool geometry. Second, execution quality only gets worse as the delay grows and as the quote-time state goes stale: a route built on a stale graph is already wrong at quote time, and a route submitted long after the quote is wrong by drift. Shrinking both is the whole job.

    Five ways to fill a trade

    It helps to ask two questions of any order-flow mechanism: (1) where and when is the price formed (off-chain on a snapshot at quote time, off-chain by a market-maker model, or onchain at execution by a competing agent), and (2) who eats the risk between quote and settlement.

    AMM smart-order routing (SOR). The price is formed off-chain by running the optimization above over public pool state, and the assembled route executes against live state. Examples: 1inch Pathfinder (DEXTools); Jupiter’s Metis engine, which precomputes liquidity graphs and split-routes under Solana’s transaction-size, parallelism, and no-global-mempool constraints (Eco); and 0x’s sample-optimize-settle pipeline (0x). The user eats all of the quote-to-settlement risk, which makes this the model most exposed to staleness and sandwiches.

    Request-for-quote (RFQ). A market maker prices off-chain and hands back a signed, firm quote good for a short window; a settlement contract checks the signature and settles atomically. The maker, not the user, carries inventory and adverse-selection risk over that window and prices it into the spread. 0x says RFQ beats AMM pricing about half the time on blue-chip pairs (0x RFQ), and because the maker signs off-chain there’s “no front-running opportunity for MEV bots” (Messari). Phantom runs its own RFQ network where the dealer commits to the price and submits the transaction, so the user gets no slippage, no sandwich exposure, and no gas to manage.

    Just-in-time (JIT) liquidity. A maker who spots a large pending swap mints a tightly concentrated LP position right before it and pulls it right after, all in one block, giving that trade deep liquidity and pocketing its fee (Uniswap). It cuts the taker’s price impact while diluting passive LPs (Delphi; IACR 2023/973), and it needs to see pending transactions, which is itself an MEV capability. JIT is basically RFQ-style active quoting wearing an AMM costume.

    Intents. The user signs a declarative order (“trade x of a for at least y of b”) instead of a transaction, and competing solvers race to fill it, computing the route at execution time against fresh state, eating the MEV/slippage risk, and handing back surplus as price improvement. CoW Protocol clears batch auctions at uniform directed prices that make intra-block ordering irrelevant (CoW Fair Combinatorial Auction; CoW Solvers); UniswapX runs a Dutch auction where fillers source liquidity wherever they like and surplus flows to the swapper (UniswapX Dutch auctions; Anoma). The mental model is different from a quote: the user signs an order, a dealer settles it by any route they choose, settlement is atomic, and the routing is opaque to the user. The price you pay is counterparty risk and new trust surface (Anoma; arXiv 2403.02525).

    Proprietary AMMs (prop AMMs). Private, single-operator AMMs (on Solana: SolFi, ZeroFi, Obric, HumidiFi) quote onchain off a live off-chain strategy, refreshing a parametric curve so cheaply (≈143 compute units versus ~150,000 for a swap) that the maker can re-price ahead of toxic flow (Helius). The measured spreads are tight and size-invariant (sub-2 bps on SOL-USDC versus 5–9 bps for classic AMMs, Chorus One), and they get nearly all their flow from aggregators (Blockworks; Delphi).

    Mechanism
    Where / when price is formed
    Who eats quote-to-settlement risk
    Staleness exposure
    Main cost
    AMM SOR
    Off-chain, quote time, on public snapshot
    User
    High (snapshot + public mempool)
    Slippage, reverts, sandwich MEV
    RFQ
    Off-chain, maker model, firm-signed
    Market maker
    Low (bounded by signature window)
    Permissioned makers; thin long tail
    JIT
    At execution, same block, via LP
    JIT maker (vs. passive LPs)
    Low for taker
    Needs mempool visibility; LP dilution
    Intents
    At execution, by competing solvers
    Solver / filler
    Low (re-priced at fill)
    Counterparty / solver trust
    Prop AMM
    Onchain curve from live off-chain strategy
    Market maker
    Low (per-block refresh)
    Opaque, single-operator

    Notice the pattern: every mechanism that beats plain AMM-SOR does it by moving price formation closer to execution and handing the leftover risk to a professional who can hedge it. They differ in microstructure, not in that underlying move.

    What a router actually is

    A router is really three subsystems, and almost everyone draws the same boxes. 0x calls it sample-optimize-settle (0x); Cube calls it discovery-computation-execution (Cube).

    Subsystem
    Job
    Decides
    Data ingestion
    Keep the graph state (every relevant edge's state) as fresh as possible
    Whether the optimization is even solving the right problem
    Routing graph
    Solve the split/hop allocation over the candidate path set
    How good the route is, given the state
    Execution contract
    Settle the chosen route atomically (composed CPI calls)
    Atomicity, slippage bound, fee capture

    The router itself is the most studied box and, oddly, the least differentiating: the allocation problem is well understood, and the edge from a smarter search shrinks as everyone converges. You can also cut routing risk by running several independent routers as an ensemble and taking the best, which is what a meta-aggregator does, at near-zero marginal cost once you’re streaming quotes. The execution contract is mostly a commodity (Jupiter v6, OKX, DFlow) you can skip building at first. Which leaves ingestion, and that’s the real bottleneck.

    The real bottleneck: a fresh graph

    Here’s the core claim: execution quality is capped by how fresh the graph state is, and you only get freshness with event-driven state. A router is only ever as good as its liquidity graph. A perfect solver on a stale graph just gives you a confident, wrong answer.

    Snapshots vs. events

    There’s a ladder of ingestion strategies, ordered by how much staleness they let in:

    1. On-demand RPC. Fetch state when a quote comes in. The number of pools a “creative” route might touch is enormous, and fetching per request bolts a network round-trip onto the critical path.
    2. Polling. Refresh the whole graph on a timer. It’s only correct at the sampling instant and decays until the next tick. Most providers live here, and it’s where “too static” quotes come from. The trend is to tighten the timer (multi-second waits down to roughly a second) but a timer is still a timer.
    3. Event-driven streaming. Subscribe to state-change events and mutate graph edges as they arrive, keeping a hot in-memory graph and only hitting the node to simulate the final transaction. This shrinks both the age of the quote-time state and, because you can re-push quotes the instant state changes, the quote-to-settlement delay.

    The case for streaming is quantitative, not just aesthetic. An onchain quoting engine re-prices every block because “even millisecond-level lag can widen spreads enough to make quotes uncompetitive” (Helius); stale data “means bad routes” (Cube). Most providers lose on both halves of the staleness story at once: a too-stale snapshot at quote time and a too-long delay to submission.

    Freshness, latency, and coverage

    Event-driven isn’t free. Roughly, the build options look like this:

    Approach
    Mechanism
    Latency to graph
    Main drawback
    Stream-processing sink
    Node → gRPC → Flink → sink → router
    Highest (many hops)
    Propagation latency; no random account access
    In-node routing
    Routing as a validator patch (shares accounts DB)
    Lowest (in-memory)
    Build coupling; resource contention; ops burden
    Co-located IPC
    Side process on the validator, shared-memory IPC
    Near-lowest
    Resource contention; bespoke deploy
    gRPC / Kafka consumer
    Yellowstone stream into a standalone service
    Moderate
    Stream provider on the critical path

    The tug-of-war is between freshness (which wants you co-located with the validator), coverage (every venue’s bespoke account layout has to be decoded, and a lot of edges bury state in PDAs you have to traverse), and the per-quote latency budget (quote flows have to avoid network calls, so the graph lives in memory). The design everyone converges on is a hot, in-memory, streamed graph with per-quote simulation, not a per-quote RPC fan-out.

    Why Solana makes this acute

    Solana sharpens the problem twice. First, there’s no global mempool, so a router can’t watch pending swaps to guess the state it will settle against. That removes a whole class of EVM staleness tricks and kills mempool-sandwich and JIT games (Eco). Second, freshness comes from Geyser, the plugin interface that streams account/slot/transaction updates straight out of validator memory (via Yellowstone gRPC or hosted variants) at sub-50 ms latencies (Triton; rpcpool/yellowstone-grpc), against a ≈400 ms slot (QuickNode). With no mempool and a 400 ms slot, the only way to keep the graph state honest is to subscribe to account-state events and patch graph edges as they land, which is exactly why Solana routing infra (Jupiter’s Metis, DFlow’s price engine) is built around streaming. An EVM router, by contrast, can read a public mempool and quote against a ~12 s block, a very different freshness regime.

    A worked example: measuring the staleness tax

    What we measured: We looked at realized slippage on a major Solana pair (SOL→USDC) for swaps that followed the simplest strategy available to a wallet: take the best aggregator quote and execute it. That baseline is the AMM-SOR row from the table above, with the user holding all of the quote-to-settlement risk.

    What we found: Taking the best quote leaves money on the table. The realized output lands a few basis points below the quoted output on average. Part of that is plain drift; part is opportunistic MEV, and the slippage distribution shows the difference: there’s a recognizable spike at a strongly negative value, the fingerprint of back-running on identifiable order flow. Either way, the gap is the drift-plus-error term from the model above, and it is a real, measurable staleness tax.

    Building a backtest: To test alternatives against the same history, we built a backtest: replay the recorded market, and model each venue’s fill as the simulated output times a fitted error factor. The fitted errors are small and slightly adverse on average, and tightest for the venues that stream continuously. In the backtest, a fill attempt only lands if the realized price still clears its target after the error; otherwise it fails and retries. One wrinkle: most providers only refresh their quotes every few seconds, so the backtest has to infer each venue’s price between samples. We reconstructed that path with a standard econometric model (a cointegrated VECM) anchored to one continuously streaming venue. In other words, the staleness problem shows up even in the tooling you build to study it.

    Testing the fix: a Dutch auction. Finally, we reframed the swap as an intent cleared by a Dutch auction, which moves price formation to execution time. At order creation the reference price is the median of provider quotes (median so one bad quote can’t move it); the auction opens a rate bump above the reference and decays geometrically toward it each 400 ms slot until a solver fills. Two results stood out. First, latency costs the user mechanically: a solver needs some number of slots (lag) to detect the order and land a fill, the price decays the whole time, and so the user’s expected take drops by roughly lag × (per-slot decay). Second, you can claw most of that back with a counterintuitive trick: let the solver submit while a fill is still slightly unprofitable (solver profit ≥ x with x < 0). The attempt isn’t checked until it reaches the chain a slot or two later, the price keeps decaying while it’s in flight, and by the time it lands it clears:

    x ≈ −(lag × per-slot decay)

    Tune x so that a target fraction of first attempts land, and you recover most of the latency penalty. (A flexible decay curve, incidentally, buys nothing over a well-chosen geometric one.) The headline: the intent mechanism recovers most of the shortfall the take-the-quote baseline was paying, at a high success rate. Moving price formation to execution time pays off, which is the general point of the next section.

    Why intents and onchain routing help

    These mechanisms attack the quote → execution variance head-on. Streaming makes your own quote-time state fresher; intents and onchain routing come at the delay from the other side, by forming the price at execution, so there’s no off-chain snapshot left to go stale. The Dutch-auction experiment above is exactly this shape, and its results are a big part of why we think execution-time pricing is where wallet order flow ends up.

    • The route is computed at the fill, on live state. Solvers and fillers compute and commit when they settle, and they carry the drift-plus-error risk themselves; UniswapX fillers source liquidity at fill and return surplus to the user (UniswapX). The Dutch auction above is a concrete instance, where the solver re-prices every slot.
    • Settlement is atomic and order-insensitive. The trade applies in one shot, and CoW’s uniform clearing price takes intra-batch ordering off the table as an attack vector (CoW).
    • Competition keeps everyone honest. A solver quoting on stale state just loses the auction to one quoting on fresher state, so staleness becomes a cost the solvers compete away rather than a risk the user holds (CoW Solvers). Prop AMMs and JIT are the limit of this, with quote and liquidity committed essentially at execution, which is why their spreads are tight and size-invariant (Chorus One).

    The same logic is why order-flow auctions (OFAs) work as a way to redistribute the MEV that staleness creates. Auction the right to back-run a user’s transaction to competing searchers, hand back the proceeds, and adversarial extraction turns into user price improvement; auction-enhanced interfaces show ~4–5 bps of average improvement (Uniswap; arXiv 2405.00537), and MEV-Blocker-style systems refund most of the back-running value (Blocknative; Flashbots).

    Now execution-time pricing doesn’t delete staleness, it moves it onto solvers and makers, and it adds new trust surface, like solver collusion and principal-agent risk (Anoma; arXiv 2403.02525), off-chain order-flow concentration, and the centralization worry around single-operator prop AMMs (Delphi). The win is just that the party now holding the risk is a professional who can price and hedge it, instead of a retail user staring at a decaying number.

    The design thesis

    Pull the threads together and one rule keeps showing up:

    Form the price as late as you can, on state as fresh as you can, and hand the leftover risk to whoever is best able to price it.

    In practice that means a handful of moves that reinforce each other: stream quotes instead of polling them; run several routers in parallel and take the best; push toward an event-driven graph fed by Geyser, so routes compute on live state and re-push the instant it changes; and lean on RFQ, intents, and OFAs, which all form the price at execution. Time-to-first-quote is the metric that keeps you honest about the first two. It’s the direction the tightest venues on Solana already operate in, and the direction we think wallet routing has to go.

    Where this breaks down

    A few honest limits:

    • The numbers are one pair, one window. The slippage study looks at a single liquid pair over a bounded period; the staleness tax and the intent fix will look different for long-tail tokens, bigger sizes, and choppier markets. Treat the figures as illustrative.
    • Freshness vs. coverage is a real frontier. Event-driven maintenance trades integration effort per venue against staleness, and the marginal value of streaming the next venue’s state versus just polling it is an open question.
    • Execution-time pricing isn’t a free lunch. Intents move risk onto solvers but add counterparty risk, fill latency, and a governance burden (onboarding solvers, watching for collusion), and how the surplus splits between user, solver, and protocol is unsettled (arXiv 2403.02525).
    • MEV moves, it doesn’t vanish. OFAs and intents push MEV up the supply chain to builders and solvers, and whether that nets out well under real-world collusion and centralization is still contested.

    The takeaway

    A swap rides on a quote, and a quote is only an estimate that has to survive to settlement. The whole order-flow zoo (AMM routing, RFQ, JIT, intents, prop AMMs) is just different answers to where and when the price is formed and who holds the risk in the meantime. A router is ingestion, graph, and execution, and the thing that actually caps quality is the freshness of the graph, which on a mempool-free, 400 ms-slot chain means event-driven streaming.

    The staleness tax is real and mostly recoverable once you move price formation to execution time. So the durable edge isn’t the cleverness of the routing algorithm; it’s the freshness of the state it runs on.

    Related articles