Documentation

Slippage & Fill Protection

Levant is oracle-priced, so there is no order book and no thin-liquidity slippage. What you are protected against instead is the spread widening — or the mid moving — between the moment you submit an intent and the moment a keeper fills it. Two on-chain guards enforce that: a mandatory slippage tolerance you set, and a hard liquidatable-at-open check the protocol sets. A fill must pass both.

What fill protection means on Levant

Every trade fills against the shared Levant Vault at a verified oracle mid price plus a deterministic spread. There is no book to eat into and no queue to be picked off in. But execution is two-step: you escrow collateral on-chain (submitOpenIntent for a market order, submitLimitIntent for a limit or stop), and a keeper fills it in a later transaction with a signed price report (executeOpen). The price you fill at is therefore a forward price, and the spread is recomputed against the book state at fill time.

That gap is the real risk to manage. Between submit and fill the mid can move, and your size plus the market's long/short skew determine how wide the spread lands. Fill protection is what caps how far that fill is allowed to drift from the mid before the keeper is forbidden to execute it.

maxSlippageBps is mandatory

Every open and every limit/stop intent carries a maxSlippageBps in the range [1, 500] — that is 1 basis point (0.01%) up to 500 bps (5%). There is no implicit default. Passing 0, or anything above 500, reverts with BadSlippage at submission. This is deliberate: a silent default is exactly how a trader ends up filled 2% away from the price they were shown.

// on submitOpenIntent / submitLimitIntent
require 1 <= maxSlippageBps <= 500      // else revert BadSlippage

// 1 bp = 0.01%     500 bps = 5%     (no implicit default)

The ceiling of 500 bps is not arbitrary: it equals the protocol's maximum spread cap (5%). The engine can never quote a spread wider than 5%, so a tolerance above that would protect against nothing. 500 bps is therefore the widest tolerance that still means something.

Note
Your tolerance is denominated in basis points of the oracle mid, not of your collateral. 100 bps = 1% of the mid price. It is a cap on price deviation, independent of your leverage or size.

How the check works at fill

When the keeper calls executeOpen, the facet takes the verified mid, computes your spread-adjusted entry, and measures how far that entry sits from the mid. If the deviation is greater than your tolerance, the whole call reverts with SlippageExceeded.

// inside executeOpen, after the spread-adjusted entry is computed
entryPrice   = mid + spread     // open long  (buy):  fills above mid
entryPrice   = mid - spread     // open short (sell): fills below mid

deviationBps = |entryPrice - mid| * 10_000 / mid    // = the total spread, in bps
if deviationBps > maxSlippageBps:  revert SlippageExceeded

Because the spread only ever moves your entry *against* you — a fill is never better than the mid — the deviation the check measures is exactly the total spread in basis points. So your tolerance is effectively a cap on the spread you are willing to accept. That spread has three parts:

spread(bps) = 2  +  notional * 100 / depth1pct  +  skewCoeff * imbalance / oiCap
            = constant   +   price-impact        +   skew

            floored at 0 (never better than mid), capped at 500 bps (5%)

// constant  = 2 bps (0.02%)
// depth1pct = 10,000,000 USDG   (notional that moves price 1%)
// skewCoeff = 100 bps at a fully skewed book
// oiCap     = 2,000,000 USDG

The practical consequence: bigger size (larger price-impact term) and trading into an already-crowded side (larger skew term) both widen the spread, and a wide-enough spread trips your tolerance. A trade that *reduces* skew pays a smaller premium and is more likely to clear a tight tolerance.

SlippageExceeded leaves your intent pending and refundable

A SlippageExceeded revert rolls back the entire executeOpen transaction. Nothing is consumed: no position is minted, no open fee is taken, and your intent stays pending with its escrow fully intact. The keeper can simply retry — the fill lands as soon as the spread narrows back inside your tolerance (for example, once skew rebalances or the mid settles).

  • The intent is not consumed — it remains pending and eligible for the next keeper attempt.
  • Your escrowed collateral stays held by the Diamond, untouched.
  • No open fee is charged and no position is created.
  • The keeper retries automatically; the fill executes once the spread fits your tolerance.
  • You can reclaim your collateral at any time with cancelIntent if you no longer want the fill.

cancelIntent is callable only by the intent's owner and refunds the full escrowed collateral (the gross amount you deposited). It reverts once the intent has already been filled, so there is never a double-spend between a fill and a cancel.

The liquidatable-at-open guard

A second guard sits immediately after the slippage check, and it is not something you configure. executeOpen refuses to mint a position that would already be at or under its maintenance margin at the mark it just filled against — it reverts LiquidatableAtOpen. Because your entry is always worse than the mid by the spread, every position opens fractionally underwater by exactly that spread. At very high leverage the entry spread alone can consume the whole maintenance buffer, and the protocol will not open a position that is dead on arrival.

// inside executeOpen, after the slippage check passes
equity = margin + PnL(notional, entryPrice -> mid)   // starts negative by ~the spread
if equity <= margin * 10% (maintenance margin):  revert LiquidatableAtOpen

// a long's opening loss ~= notional * spread = margin * leverage * spread
// dead-on-arrival roughly when  leverage * spread  >=  90%
// e.g. at 100x, an entry spread near ~0.9% already fails this check

The maintenance margin is 10%, so the buffer between opening and liquidation is 90% of margin. A position whose opening loss (its own spread) eats through that buffer would be liquidatable in the very next block — the trader would pay the open fee only to be immediately liquidated. Refusing the mint protects the trader from that, and it protects the vault from churn. The rollback semantics are identical to SlippageExceeded: the intent stays pending, no fee is taken, and you can retry (with lower leverage or smaller size) or cancelIntent.

Warning
A generous slippage tolerance does NOT bypass the liquidatable-at-open guard. At extreme leverage, widening maxSlippageBps only lets the fill clear the price check — the position can still be refused for being dead on arrival. If a high-leverage open keeps reverting, the fix is to reduce leverage or size, not to loosen slippage. Neither guard ever costs you a fee.

Why both guards exist

The two checks defend different things and are fully independent — a fill must clear both. One is about *price quality*, which is yours to set; the other is about *not opening straight into liquidation*, which the protocol fixes.

GuardProtects againstTrader-configurable?Trips whenOn trip
SlippageExceededFilling at a worse price than you agreed toYes — maxSlippageBps, 1–500 bpsEntry deviates from the oracle mid by more than your toleranceReverts; intent stays pending; escrow refundable; no fee
LiquidatableAtOpenMinting a position that is already liquidatableNo — fixed protocol invariantEquity ≤ 10% maintenance margin at the fill markReverts; intent stays pending; escrow refundable; no fee

Together they bound what the keeper can do. The keeper chooses the timing and signs the price, but it can only ever fill you at a verified oracle price, within the slippage tolerance you signed, and never into an immediately liquidatable position. It is non-custodial: your funds are escrowed by the Diamond, never held by the keeper.

Setting Max slippage in the UI

In the order ticket you choose a Max slippage preset — 0.10%, 0.30%, 0.50%, or 1.00%. That value is your maxSlippageBps (10, 30, 50, or 100 bps). The app never submits an intent without one, so there is no way to fire an order with an unset tolerance.

Max slippageIn bpsTypical use
0.10%10 bpsDeep majors (BTC, ETH), small size, calm markets
0.30%30 bpsLiquid markets, moderate size
0.50%50 bpsLarger size or thinner markets
1.00%100 bpsVolatile conditions, or size large enough to move skew

The trade-off is direct. A tighter tolerance gives stronger price protection but a higher chance the fill is rejected and retried when markets are fast or the book is crowded. A looser tolerance fills more reliably but accepts a wider spread. Pick the tolerance to your size and the market's depth, not the other way around.

  1. 1Open the order ticket and choose your market, direction, size, and leverage.
  2. 2Set Max slippage to the tolerance you are willing to accept (this becomes maxSlippageBps).
  3. 3Submit — your collateral is escrowed on-chain by the Diamond.
  4. 4A keeper fills at the next verified mark if the spread fits your tolerance and the position is not liquidatable-at-open; otherwise the intent stays pending and retries.
  5. 5If it is not filling and you have changed your mind, call cancelIntent to refund your escrow in full.

Scope: limits, closes, and liquidation

The slippage tolerance guards the open path. Market opens and limit/stop entries both carry maxSlippageBps and both are checked in executeOpen. A limit or stop additionally cannot fill before its trigger is crossed — the keeper's fill reverts NotTriggered until the mark reaches your triggerPrice, so a limit can never fill at a worse-than-trigger price *and* is still bounded by your slippage tolerance on top.

Closing applies the same deterministic spread to the mid (a close pays spread at the other end of the round trip) but does not carry a separate per-close slippage tolerance in this version. Liquidation is different again: it settles at the raw oracle mark with no adverse spread applied, and only ever affects the one liquidated position (margin is isolated).

Note
Testnet reality: Levant runs on Robinhood Chain mainnet, is unaudited, and its testnet tokens have no monetary value. The oracle is 1-of-1 today. Leverage can lose your entire margin, and both guards protect the fill — not the trade's outcome. This is not investment advice.