Velocity ProtocolDevelopers
Trading

How fills work

What happens between sending an order and holding a position: who fills it, in what order, and against what.

Velocity has no central matching engine. Orders live onchain as accounts, each keeper builds its own copy of the book offchain, and anyone can submit a transaction that fills an order. Execution is therefore best-effort rather than guaranteed. What the program owns is not the matching but the rules: given an order and the counterparties someone brought to it, it decides who fills, in what order, and at what price. See Decentralized orderbook.

The order, as the program sees it

An order carries an auction duration, an auction start and end price, a limit price, and an expiry. The first three define a line the price walks along; the limit price is what the order accepts once the walk is over. See Auctions.

During a limit order's auction it can only take liquidity. Once the auction ends it is resting, and can take or provide. A post-only order skips the auction and only ever provides.

Getting the order to somebody who can fill it

Once the transaction lands, the order exists onchain and propagates to keepers and market makers, who race to fill it, because filling pays. That race takes three shapes, differing only in who submits the fill:

  • A keeper fills it against resting liquidity. Keepers build their own orderbooks offchain, find a resting order on the other side of the taker's, and submit a transaction naming both.
  • The order's owner fills it. Nothing about the fill is privileged, so a trader can run a filler for its own orders. That removes the dependence on someone else's bot; it does not make a fill guaranteed.
  • A market maker fills it with just-in-time liquidity. The maker places an immediate-or-cancel post-only order solely to fill the taker's, fills it, and cancels the rest, in one transaction. A JIT order never rests on the orderbook.

One taker order through a JIT auction

Takeroff-chainProgramon-chainEvent feedon-chainJIT makersoff-chainKeeperoff-chainAuction opensAuction windowAuction ends unfilledTaker to Program: Order with auction paramsOrder withauction paramsProgram to Event feed: Taker order eventTaker ordereventEvent feed to JIT makers: New auctionNew auctionJIT makers to itself: Price at this slotPrice at thisslotJIT makers to Program: Place and make fillPlace and make fillProgram to Taker: Filled at auction priceFilled at auctionpriceAny maker can fill any part of the order, first come firstserved, so partial fills are normal.Keeper to Program: Fill from DLOB or AMMFill from DLOB or AMMResting orders and the AMM are matched by price ateach level, not in a fixed order. The taker can submit thisfill themselves instead.Program to Taker: Fill, or the order expiresFill, or the orderexpires
Shows the order of events for a single taker order, from placement to a maker fill or to the fallback after the auction. Sources: the JIT FAQ, JIT Auctions, and Matching Engine pages in these docs. The event feed is the on-chain event emitter that makers subscribe to. The auction price ramps from the taker's best price toward their limit as slots pass, so filling early costs a maker more. Durations are counted in Solana slots, not seconds, and a limit order still open when its auction ends rests on the DLOB, where it can then fill as a maker.

Filling is permissionless and first-come first-served. The filler is paid the lesser of 10% of the fee and a time-based reward that grows with the fourth root of the order's age from a base of $0.01, which is why old orders get filled before large ones. A keeper who cancels an expired or reduce-only order is paid a flat $0.01; an account cancelling its own order pays only the Solana network fee.

How the program builds a fill plan

The program takes the order, the makers the fill transaction brought along, and its own AMM, and turns them into an ordered list of steps.

How one taker order fills

Taker order to Take the next best price levelTake the next best price level to Is the AMM quote better than this level?yesyesnonothenthenBest quote at this level fills to Size left, and more levels?yesyesnonoResidual AMM fill to Fill completeTaker orderyour limit caps fillsTake the next best pricelevelbest price the filler bringsIs the AMM quote betterthan this level?AMM fills firstup to this priceBest quote at this levelfillsresting or JIT makerSize left, and more levels?Residual AMM fillif size still crossesFill complete
Shows how a single taker order fills across price levels. Source: Matching Engine and Orderbook and Matching in these docs. There is no fixed JIT, then orderbook, then AMM order: at each level the AMM quote and the best maker quote (resting or just in time) compete on price, and the AMM comes last only for the residual fill. The docs do not say which side wins when two quotes tie on price, only that a priority flag decides. The walk is per fill transaction and best effort: a filler can only match the maker orders it includes, so a better resting order can be skipped. Among tied resting orders, the earlier one fills first.

Makers arrive best-price-first from the taker's point of view, and the program stops at the first one the taker's limit price does not cross, since everything after it is priced worse. Wherever the AMM quotes better than the next maker, an AMM step is slotted in ahead of that maker and bounded at the maker's price. The plan therefore alternates in strict price order, and one order can fill partly against makers and partly against the AMM in a single transaction.

Each AMM step is capped at roughly 1% of the AMM's reserves, per transaction rather than per order, so a larger order can still fill against the AMM across several transactions.

There is no router pass, no liquidity source quoting a ladder, no routing priority between sources, and no last look for the AMM. Those describe a design that was never deployed.

Self-trade prevention

An account cannot fill against itself. Its own resting orders are skipped before prices are compared, so they never enter the plan. A skipped order is not cancelled or modified; it stays open for somebody else to match.

Who counts as a maker

A maker must be a resting limit order, meaning its own auction is over. When both sides are resting, the one whose auction ended first is the maker, which is how the program preserves time priority without an onchain book.

What this means for takers

Liquidity looks deeper than the AMM's curve because most of it is not on the curve. JIT liquidity is not constrained by the AMM's virtual reserves; it is whatever external makers bring to the auction. Every order runs its own auction, so there is no queue between traders, and an account can have as many open as it has order slots, which is 32.

Partial fills are normal and are not a failure mode. There is no fill-or-kill on Velocity: an auction fills up to the order's slippage tolerance and leaves the rest working, and the remainder can be cancelled at any time. An order is left partly filled because its slippage tolerance was never crossed, because it expired, because the per-transaction AMM cap was reached, or because no filler was watching when the price was right.

That last one is the honest cost of a permissionless orderbook, and it is why the filler reward grows with order age.

The price the app shows before an order is sent is the AMM's quote for that size. It is not the worst case. The worst case is the order's own auction end price or limit price, and a resting maker priced worse than the AMM can still fill part of the order once the AMM's liquidity at the better price is consumed. Read the limit, not the estimate.

What this means for makers

Resting orders are placed once and left. JIT liquidity means answering individual auctions inside their window, which needs low-latency infrastructure and captures flow that never reaches the book. Both earn the same flat 0.25 bps maker rebate on filled notional. A JIT maker is its own filler: fill and placement are one transaction, so a partially filled JIT order cannot be pulled.

Whether the AMM competes with JIT makers inside a match step is governed by the market's just-in-time intensity, an admin-set per-market dial. While that intensity is zero the match step is the orderbook maker alone; above zero the AMM can co-fill beside the maker at the maker's price. Read the live PerpMarket account for the value.

Liquidations do not run through this path at all. A liquidator either takes over the position directly or closes it against resting liquidity or the AMM's curve. See Liquidations.

For running a maker or filler in practice, see JIT auctions, JIT-only market making, and the JIT maker bot tutorial. Error codes and RPC problems are covered in Trading automation troubleshooting.