Limit Order v2: The Any-Any Problem

Part 1 of 3: Engineering Limit Order v2. Parts 2 and 3 coming soon.
You set a limit buy at $1.00. The price wicks down to $0.998 right in front of you, and your order just sits there. A couple of seconds later, it fills.
If you have used limit orders on Jupiter, you have felt that half-beat of lag. It was the single most common thing users told us about. You were not imagining it, and you were not alone.
That delay is not a bug. A Jupiter limit order is passive: it watches the market and acts when your condition is met, executing through whatever liquidity already exists across Solana's DEX ecosystem. It consumes liquidity, it does not provide it. And watching, then acting, always costs something.
A note on terms. By "limit order", we do not mean a resting order in a central limit order book, the kind other participants can match and route against. We mean a trigger-based order that watches a price and fires a swap. Throughout this series, when we reference similar systems, we mean architecturally similar trigger-based systems, not order books.
But "watch, then act" only explains part of the lag. The deeper part, and the part we could actually do something about, came from what it takes to watch the price at all. Not the price of one pair. The price of any pair, between any two tokens a user might pick. That is where this story begins.
The "any" cost
Most limit order platforms on Solana support a curated set of token pairs. Pick from a list. Maybe twenty pairs, maybe fifty. The infrastructure behind this is simple: a price feed per pair, check each order against it, and trigger when the price crosses. It's a lookup table. The challenges are operational, not structural: keep the feeds fresh, survive downtime. The shape stays flat: you watch as many things as the pairs you chose to support.
Jupiter's Limit Order system supports any token to any other token for one simple reason: to serve as many users as possible.
On a curated platform, you can only place orders on pre-approved pairs. If your desired pair isn't on the list, you can't even create the order. On Jupiter, you can open a limit order on any pair, the moment you want to. The order then waits. It executes as soon as two conditions hold: a route exists between the two tokens, and your price condition is met. There's no approved-pairs list to get onto, the default is open. If we can find a route, we can fill it.
That single product decision transforms price monitoring from a lookup problem into a combinatorial one. The number of pairs you might have to price grows with the number of tokens, and quadratically at that.
Every new token that enters the Solana ecosystem adds not a single pair but an entire row and column to the matrix of pairs you could be asked about. The set is finite at any instant, but it has no ceiling you control.
The question stops being "do we have a price for this pair" and becomes "how do we obtain the price for every possible pair and permutation that any user could care about, at any moment, without falling over."
This is the engineering story of how we answered that question twice. The first answer was reasonable. The second one was right.
The barter problem
In a dollar-denominated world, price feels like a property of a thing: "SOL is $180." But that's only because the dollar is so universal, you forget it's there. Take away the shared denominator, and the price becomes a ratio between two specific assets. On Solana, that's exactly the situation, tokens trade against each other in pools, and each pool defines its own exchange rate. There is no default "price", only pairwise rates. To know whether a user's SOL/JUP limit order should trigger, you need the SOL/JUP exchange rate specifically. The SOL/USDC rate alone won't tell you.
Now scale that. For distinct input tokens and distinct output tokens across all open orders, you need up to exchange rates:
That's not a list. That's a matrix. And every new token that appears on either axis adds an entire row or column. The cherry on top? These rates are not symmetric.
In v1, we repurposed Jupiter's routing engine as a price feed. The routing API could compute the best exchange rate between any two tokens. That was its job, so we called it. For every unique input mint across all open orders, the keeper (an off-chain process that monitors and executes orders) would batch-query the routing engine for exchange rates against all its paired output mints, and compare the answers to each order's trigger condition. This was the right call given what existed at the time. There was no in-house price stream. The routing API was the only oracle that could answer any-any price queries. It worked.
The ceiling
As Jupiter grew, so did the order matrix. More users brought more tokens. More tokens meant more unique input mints in the open order set. More input mints meant more routing calls per tick.
Stablecoins helped somewhat as a proxy denominator. We could get the exchange rate of each token against USDC and compare them to approximate any pair's rate. This did linearise the search space: instead of pair lookups, you get lookups against USDC and derive the rest. But it was, at best, an approximation. The rate of A through USDC to B isn't always the same as A directly to B, or A to B through a different proxy like SOL or USDT.
For triggering, an approximation was sufficient to determine whether an order might be ready.
For execution, it wasn't.
You still needed the actual route plan. A false-positive approximation wasted downstream CPU cycles and RPC calls from the routing engine. Worse, the latency introduced from the time of check to the time of use (TOCTOU) negatively impacted slippage, which could violate the contract's guaranteed output invariant.
We attacked the cost from every angle the architecture allowed. Each lever bought headroom; none of them broke the coupling underneath.
Pulled quote computation out of the trigger loop. Reverted inline route resolution; a background poller refreshes exchange rates on its own ~1s cadence while the loop simply reads them. Made the loop predictable. But prices are up to one cycle stale, and staleness is exactly what bites at execution.
Buffered the trigger thresholds. A small margin on each comparison lets an order near its boundary fire early, deliberately preferring a false positive to a missed fill. But every false positive spends a full route computation and RPC budget downstream, only to discover the order isn't actually fillable yet.
Batched outputs per input mint. Orders grouped by input mint and quoted against all paired outputs in a single call. Call count dropped from per-order to per-input-mint. But each call now fans one input across every paired output and every pool, so the heavy routing work moved inside the call rather than going away.
Suppressed dead pairs and paced the loop. A short-lived cache skips pairs the router just reported no route for; a minimum loop duration caps how fast the keeper hits the routing engine. Trimmed obvious waste and protected the engine. But the only thing left to throttle was responsiveness itself: slowing the loop slows every trigger.
But the fundamental cost grew with every new input mint, and each call was doing heavy routing work across all its paired outputs. No amount of operational tuning changes that coupling.
The breaking point was never a crash, it was the variance.
Every monitoring pass recomputed prices for every unique mint in play. We were constantly asking, instead of being told. Cost grew with the mint count, so responsiveness drifted with load, and orders on thinly traded tokens waited longest, sometimes long enough that the price had already moved on.
Every fix only shifted the variance around; none removed the coupling underneath. And to us, inconsistent is worse than slow.
This tension planted a seed. If a common denominator could linearise monitoring even as an approximation, what would happen if we revamped the system around it? That idea of a proper USD price for every token, not just a USDC proxy, to trigger and execute on, is where the story picks up next.
The inversion
The breakthrough wasn't a faster way to compute pair prices. We were at the point of diminishing returns for that. It was realising we didn't need pair prices at all.
@jarxiao put it precisely: "money collapses quadratic complexity into linear complexity".
Without a unit of account, you need a rate for every pair, that's a matrix:
With a unit of account, you store one price per token and derive any pair:
That's exactly what happened here. When the team built an in-house price stream that continuously derives USD prices for tokens from on-chain swap activity, the structure of the problem changed. Instead of maintaining every open pair's exchange rate, you only need the USD price of each individual token. Quadratic becomes linear.
It also changed how prices reach us: a list of per-token prices is small enough to stream, where a matrix of pairs never could be. We stopped polling and started receiving: pull became push.
Here's what it looks like in practice: the trigger system builds an inverted index of open orders, grouped by their trigger token. It subscribes to USD price events for those tokens. When a new price arrives for a particular token, the system looks up every open order indexed to that token and evaluates them in a single pass. Orders that have crossed their threshold get marked for execution.
The cost of trigger detection is now proportional to the number of tokens with recent price activity in each block - not the total number of open orders, not the number of unique pairs, not the trade size of the open orders. A token with no swap activity in a given block costs nothing to evaluate. A token with heavy activity gets evaluated once, regardless of how many orders reference it.
The routing API is no longer on the critical path for trigger decisions. It re-enters the picture only at execution time, when the system needs an actual route plan, a fundamentally different and much less frequent operation.
The honest tradeoff
This design is not a free lunch.
For the USD price of a token to exist in the stream, that token needs recent swap activity - someone, somewhere, needs to be trading it against something that has a known USD price. The price graph isn't always connected. A token that only trades against one obscure counterpart, which itself only trades against another obscure counterpart, may never surface in the USD price stream at all. The signal simply doesn't reach.
This is a genuine hard problem: price connectivity in a disconnected token graph. If you imagine every token as a node and every actively traded pool as an edge, the USD price stream can only reach tokens that are connected, through some path of recent swap activity, back to tokens with established USD prices. A pool can exist, but if nobody's trading on it, it produces no price signal. Isolated clusters in that graph are invisible.
For users with orders on illiquid tokens, this means delayed triggers. Not incorrect, just delayed ones. The system will not trigger an order based on a stale price, because stale prices are exactly the wrong input for an execution decision. If the last trade was thirty minutes ago, you shouldn't be executing a limit order based on that number. The price could be anywhere by now.
This is a conscious design tradeoff. The team is actively working on extending price connectivity, finding ways to surface prices for tokens that sit further from the well-traded core of the graph. But the default behaviour, for now, is the safe one: if we don't have a fresh price, we wait. That's better than the alternative.
The result
Two things changed, and both matter.
The cost per check dropped dramatically. v1's USDC trick had already linearised the call count to . But each of those calls still hit the routing engine, which had to compute the optimal swap path from one input mint to every output mint paired against it, across every pool on Solana. That graph traversal, across the entire liquidity network, was the cost that scaled.
In v2, a trigger check is a database comparison against a USD price that arrived passively. The route plan is only needed after the order has been marked for execution.
The cost driver shifted from proactive to reactive. v1 polled every unique input mint with open orders, every cycle, whether or not anything had changed. v2 only evaluates tokens that had recent swap activity in a given block. If a token had no swaps, it costs nothing. The complexity moved from the full token set to only those with recent activity, which is often a much smaller set.
The result: the set of orders can grow by an order of magnitude, and the cost of deciding what to trigger barely moves. A hundred thousand open orders across a thousand tokens cost the same as ten thousand open orders across the same thousand tokens, because the cost is driven by price events, not by order volume.
The any-any constraint, which was once the system's most expensive property, became architecturally neutral. A SOL/USDC order and a JUP/WIF order are evaluated by the same mechanism, at the same cost, in the same pass.

All of that infrastructure, the matrix collapse, the push-based pricing, the inverted index, comes down to one thing for users: limit orders now work the way you'd naturally think about them. Pick a price in dollars, the system watches and swaps when it hits. No more wondering which token pair the price refers to, no more mental conversion. You set a number, and it does what you meant.
Next in the series: Correctness Under Failure - why triggering is the easy problem. Executing correctly, when RPC calls drop, processes crash, and the blockchain confirms transactions your service never saw, is where systems silently fail. Part 2 is about how you build durability and atomicity when you can no longer lean on the chain to do it for you.