Skip to Content

Velocity AMM

The Automated Market Maker (AMM) is Velocity’s source of guaranteed constant liquidity for the exchange.

Introduction

Velocity uses a backstop AMM as a source of guaranteed constant liquidity, providing constant liquidity to be taken against asynchronously.

Velocity’s AMM design builds on Perpetual Protocol’s innovation, the virtual AMM (vAMM) , with configurable parameters, namely:

  • Peg: a price multiplier

  • K: liquidity depth

  • Fee Pool: the AMM’s retained equity is its own spread surplus plus an admin-configured share of the net taker-fee remainder (FeeStructure.amm_fee_numerator, currently 0). Fee discounts already exist as volume-based fee tiers.

  • Shared Curve Budget: peg adjustments (repeg) and k adjustments draw from a single shared budget: the AMM’s retained fees (net of distributions). There is no separate allocation per operation; whichever adjustment runs spends down the same pool. Funding payments are not part of this budget: outflows the AMM pays in funding are separately capped at 1/3 of the AMM’s retained equity per funding period, so a sustained imbalance can’t drain the AMM in one period.

Velocity’s AMM

Velocity’s AMM is still an AMM using a constant product curve but now includes external liquidity, a concentration factor, and dynamic spread/peg that programmatically update prior to filling trades.

Liquidity Concentration

The curve (x * y = k) is bounded on both sides by min_base_asset_reserve and max_base_asset_reserve: the reserves at which the curve is considered exhausted and stops quoting further. concentration_coef sets how tightly those bounds sit around the AMM’s balanced (terminal) reserves, i.e. sqrt_k:

max_base_asset_reserve = sqrt_k * concentration_coef / CONCENTRATION_PRECISION

min_base_asset_reserve = sqrt_k * CONCENTRATION_PRECISION / concentration_coef

A concentration_coef close to CONCENTRATION_PRECISION (1x) pulls both bounds in tight around sqrt_k: liquidity is concentrated near the peg, so trades close to mid get the lowest slippage, but the curve exhausts sooner as inventory skews. A larger concentration_coef (up to MAX_CONCENTRATION_COEFFICIENT, ≈1.4142x) pushes the bounds further apart, spreading the same k across a wider reserve range: more headroom for inventory to skew before the AMM stops quoting, at the cost of higher slippage near the edges of that range.

Admins tune this indirectly through a concentration_scale parameter rather than setting concentration_coef directly: concentration_coef = CONCENTRATION_PRECISION + (MAX_CONCENTRATION_COEFFICIENT - CONCENTRATION_PRECISION) / concentration_scale. A larger scale yields a coefficient closer to CONCENTRATION_PRECISION (tighter, more concentrated liquidity); a smaller scale (minimum 1) pushes the coefficient toward its MAX_CONCENTRATION_COEFFICIENT ceiling (looser, more spread out). An update is rejected outright if the resulting bounds would no longer bracket the AMM’s current net position.

The mechanics behind the dynamic spread and peg are:

  1. Inventory adjusted spreads

  2. Oracle live pricing/volatility

Inventory Adjusted Spreads

In a typical constant product AMM, the AMM quotes the same price for buys/sells and charges a flat fee.

To implicitly utilise a dynamic fee, Velocity’s AMM quotes different prices for buys and sells. The offset from the mid-price is dynamic based on the current inventory.

To do so, there will be 3 points tracked on the constant product curve: the bid price and ask price, and a point between the reservation price.

Velocity AMM Bid-Ask Spreads

Base long spread and short spread are currently set as:

bid/ask price = reservation price ± base_spread + f(inventory, balance, ...)

The long and short spread can be asymmetric due to changes in the underlying inventory skew and market buy/sell pressure. Ultimately, the ask - bid can never exceed the max spread (percentage calculated using the current valid oracle price).

The bid/ask points on the curve are calculated as follows (exact efficient integer math method calculated on contract):

bid_quote_reserve = quote_reserve - (quote_reserve / (100% / ((short_spread - reference_price_offset) / 2)))

ask_quote_reserve = quote_reserve + (quote_reserve / (100% / ((long_spread + reference_price_offset) / 2)))

Only half of each spread is applied to the reserve shift (the spread is a bid-ask width, so each side moves by half of it from the reservation price), and reference_price_offset shifts both bid and ask together when the reservation price itself is offset from the peg-implied price. The quote_reserve is defined as the total AMM reserves of the quote asset. Then using the same k to back out the base_reserve for the bid/ask curves as well.

What drives reference_price_offset: unlike the spread, which widens the band around the reservation price, reference_price_offset shifts the whole band together, a move of the reservation price itself. It’s only active while curve_update_intensity > 100 (held at zero below that). Between 101 and 199, its magnitude is bounded by the lesser of (curve_update_intensity − 100) bps and half of max_spread. At 200 or above, it is bounded by the greater of half of max_spread and 100 bps, so 100 bps becomes a floor rather than a ceiling at that point. The offset tracks a blend of the mark-vs-oracle premium over several windows (a fast twap, a slower twap, and the 24h average funding rate expressed as a price premium, each clamped to the max offset and then averaged) combined with the AMM’s inventory skew (how close its net position sits to its available liquidity on either side, past a configurable deadband). It only applies when the sign of that premium agrees with the sign of the inventory skew, i.e. when market pricing and the AMM’s own inventory lean the same way, otherwise the offset is zero. A sign flip between updates is smoothed across slots rather than snapping instantly. In effect, when the AMM is carrying a persistent premium/discount that lines up with which side it’s already leaning, reference_price_offset nudges both bid and ask toward flattening that inventory, without changing the spread’s total width.

Oracle Live Pricing

The AMM’s reservation price gets updated regularly through Velocity’s live oracle-based pricing.

This pricing mechanism updates an asset’s mark price towards its oracle price, achieving:

  • more accurate pricing; and

  • better entries and exits closer to oracle price.

The mark price is updated each time the oracle updates its price and when the contract is interacted with.

Oracle Live Pricing Sequence

Every time a trader wants to fill against the AMM, the AMM will be updated in the same slot with a valid oracle price (see Oracles):

  1. Check the oracle price: the AMM checks the oracle price.
  2. Move peg toward oracle: the AMM moves its peg toward the oracle price.
  3. Set bid/ask spread: the AMM sets its bid/ask spread versus its reservation price.
  4. Fill order, the order fills according to the bid/ask price once it is eligible for an AMM fill: either the order predates the MM oracle’s delay window, or it is a liquidation or safe-trigger order, which can fill against the AMM immediately.

The overall spread will increase if the AMM is heavily levered or in debt. The oracle price and reservation price are always within this spread. Effective leverage is a function of many market stats, including Inventory P&L and collateral within the fee pool in the terminal state.

The reservation (AMM) price is the quote/base reserves * peg.

AMM JIT Participation

Beyond quoting its own curve, the AMM can also co-fill a resting orderbook maker order just-in-time (JIT): stepping in alongside that maker, at the maker’s price, when doing so improves the AMM’s own inventory. This trades away some of the AMM’s natural curve spread in exchange for rebalancing.

JIT only fires against resting maker orders. Filling directly against the AMM’s own curve still requires the order to be eligible for an AMM fill (see above).

When the AMM will JIT-make

The AMM only considers JIT-making a fill when all of the following hold:

  • amm_jit_intensity is non-zero. This is the market’s admin-configured JIT dial, ranging 0–100. 0 disables JIT participation entirely; larger values scale up how much of a fill the AMM is willing to take (see sizing below).
  • The fill would improve the AMM’s own balance. The AMM only offers JIT liquidity when the taker’s flow works to reduce the market’s net imbalance, not when it would add to it.
  • The taker isn’t already guaranteed to land on the AMM’s own curve next round. A market order with no limit price, once its auction completes, fills on the curve automatically on the next round if the resting maker cannot cover the taker’s whole remaining size, so residual flow is guaranteed to reach the curve anyway, JIT is skipped in that case rather than filling the same flow twice.

Sizing a JIT fill

Sizing is built from several caps, applied in sequence:

  1. 50% of the fill size. The AMM can never take more than half of the matched fill amount (the smaller of the taker’s and maker’s remaining size): max_jit_amount = fill_amount / 2.
  2. Wash-trade guard. With no valid oracle price, JIT participation is disabled outright for that fill. Otherwise, if the fill price sits on the wrong side of oracle by more than 5 bps (a taker buying below oracle − 5bps, or selling above oracle + 5bps), the 50% cap from step 1 is shrunk further, scaled down by how far the price has strayed past that 5 bps band relative to the AMM’s own opposite-side spread. The further past the band, the smaller the AMM’s participation.
  3. Imbalance-based sizing. The AMM compares its maximum open bids against its maximum open asks (the same bounds concentration_coef sets, see above). If the larger side is at least 1.5x the smaller side, the book is considered imbalanced and the AMM is willing to take up to the full size of the fill; otherwise it caps itself to 25% of the fill size.
  4. Intensity scaling. The step 3 result is scaled by amm_jit_intensity / 100: an intensity of 50, for instance, halves whatever step 3 allowed.
  5. Inventory-flip guard. The intensity-scaled amount is capped at the AMM’s current absolute net position: a JIT fill can flatten the AMM’s inventory but never flip it onto the opposite side.
  6. The final JIT size is the smaller of the wash-guard-adjusted cap from steps 1–2 and the imbalance/intensity-scaled amount from steps 3–5, standardized to the market’s order step size.

Every JIT fill happens at the resting maker’s price, not the AMM’s own curve price: the gap between the two is the cost (or benefit) the AMM absorbs in exchange for rebalancing.

vAMM maker rebate

The AMM can also be paid a maker rebate on the fills where it is the maker. This is gated by the VammMakerRebate bit on State.featureBitFlags, which is off by default and toggled exchange-wide (there is no per-market version of it). Enabling it is a cold-admin action; disabling it can be done by any signer holding the FeatureFlag role.

How the rebate is computed and where it lands:

  • The rebate rate is the base tier maker rebate (FeeStructure.feeTiers[0]), currently 0.0025% of the filled notional, and is then scaled by the market’s feeAdjustment. It deliberately does not use the taker’s fee tier, so the AMM’s earnings do not vary with which account happens to be taking.
  • It is carved out of the taker-fee remainder before that remainder is split between the AMM fee provision, the insurance fund, and the protocol, then folded into the AMM’s amm_fee for the fill. If the remainder is too small to cover it, the rebate is clamped to what is left.
  • It accrues into the AMM’s fee provision and is tracked in the market’s cumulative feeLedger.ammProtocolFeesReceived, the same counter a perp bankruptcy claws back against.

The taker pays exactly the same fee whether the flag is on or off. What changes is who receives that slice of the fee: with the flag on, part of the protocol and insurance-fund legs is redirected to the AMM. See Trading Fees for the full fee waterfall.

Last updated on