Slippage & Fill Estimation#
How Seesaw previews what your order will actually cost before you sign — and what protects you on-chain after you do.
In plain English#
When you submit an order, the app shows a fill preview: how many shares will likely fill, at what average price, the worst price you'd touch, and the fee. That preview is computed by walking the live order book level by level, exactly the way the on-chain matching engine would.
Two numbers in the preview are easy to confuse:
| Term | Definition | Question it answers |
|---|---|---|
| Slippage | Average fill price vs. the best price on the book when you arrived | "How much worse than the top of book do I pay on average?" |
| Price impact | Worst (deepest) fill price vs. the best price on the book | "How far into the book does my order push the price?" |
Both are expressed in basis points (bps; 100 bps = 1% of a $1 share) and both are zero when your whole order fills at the top of the book.
The preview is an estimate. The binding protection is on-chain: your limit price (Limit / Post-Only orders) or your
worst_acceptable_price_bpsbound (Immediate-or-Cancel orders). The program will never fill you outside those bounds, no matter what the book looked like when the preview was rendered.
Why a binary order book is special#
Seesaw keeps a single canonical order book of YES shares per market (see Order Book). NO orders are mapped onto it by price complement:
| Your order | Canonical book action | Consumes |
|---|---|---|
| Buy YES @ q | Bid @ q | Asks (lowest first) |
| Sell YES @ q | Ask @ q | Bids (highest first) |
| Buy NO @ q | Ask @ (10000 − q) | Bids (highest first) |
| Sell NO @ q | Bid @ (10000 − q) | Asks (lowest first) |
So a fill estimate for a NO order has to walk the opposite side of the YES
book and translate every price through 10000 − p before showing it to you.
The estimator does this for you — all prices it returns are in your
denomination (NO prices for NO orders).
The unified estimator#
Web, mobile, and the SDKs all share one implementation:
estimateFillFromLevels in @seesaw/core
(packages/core/src/orderbook.ts). It takes aggregated book levels (the
shape served by the indexer or produced by aggregateOrderbook) and a
prospective order, and returns a FillEstimate. For the full function
signature, all FillEstimate fields, and a code sample, see
SDK: Reading Data — Estimating Fills and Slippage.
How the walk works#
Key rules of the walk:
- Levels are consumed best-first — ascending asks for buys of YES, descending bids for sells of YES (and the complements for NO orders).
- The walk stops at your price bound. For Limit orders that's your limit
price (converted to canonical coordinates). For IOC orders with a
worst_acceptable_price_bps, that tighter bound is used instead — unless it's the disable sentinel (0for canonical bids,10000for canonical asks), in which case the limit price applies. - PostOnly never takes liquidity, so its estimate always shows zero filled and the full quantity resting.
- The fee is computed once on the average canonical price, using the same capped-linear-decay curve the program applies (see Fee Structure).
Worked example#
Canonical YES book (price in bps × quantity in shares):
Asks (sell YES): Bids (buy YES):
5500 × 100 5300 × 150
5800 × 200 5100 × 250
6200 × 300
Order: Buy 250 YES, Limit @ 6000 bps (0.60 USDT).
- Reference price = best ask = 5500.
- Walk asks ascending, respecting the 6000 bound:
- 100 shares @ 5500 → 150 still needed
- 150 shares @ 5800 (5800 ≤ 6000, take it) → done
- next level 6200 > 6000 → would have stopped here anyway
- Results:
| Field | Value | Working |
|---|---|---|
filled | 250 | 100 + 150 |
remaining | 0 | |
avgPriceBps | 5680 | (100×5500 + 150×5800) / 250 |
worstPriceBps | 5800 | deepest level touched |
refPriceBps | 5500 | best ask before the order |
slippageBps | 180 | 5680 − 5500 |
priceImpactBps | 300 | 5800 − 5500 |
cost | 142.00 USDT | (100×5500 + 150×5800) / 10000 |
feeAmount | 2.84 USDT | fee_bps(5680) = min(200, 600×4320/10000) = 200 → 2% of 142, rounded up |
totalCost | 144.84 USDT | cost + fee |
If the same order had a limit of 5600, only the first level fills:
filled = 100, remaining = 150, and (as a Limit order) the remainder
rests on the book at your limit price — restsRemainder = true.
NO-side example: Buy 100 NO @ 4800 bps (0.48 USDT).
- Canonical mapping: Buy NO @ 4800 → Ask @ 5200 on the YES book → consumes bids, highest first.
- Best bid is 5300 ≥ 5200, so 100 shares fill at canonical 5300.
- Translated back to your denomination: you pay 10000 − 5300 = 4700 bps (0.47) per NO share — better than your 0.48 limit. The reference price is also 4700, so slippage and impact are both 0.
Why the preview can differ from reality#
The estimate reads a snapshot of the book; the chain executes against the book as it exists when your transaction lands. The preview does not model:
- Book movement in flight — other orders may land before yours.
- Self-trade handling — the program skips your own resting orders.
- Tick rounding of the resting remainder — bids round down, asks round up to the market's tick (default 100 bps).
- Per-fill fee variation — the on-chain fee is computed per fill; the preview computes it once on the average price.
That is why the on-chain bounds — not the preview — are the contract:
| Order type | What binds on-chain |
|---|---|
| Limit | Your limit price: no fill at a worse price; remainder rests at your price |
| PostOnly | Never takes; rejected (WouldCross) if it would match immediately |
| IOC | worst_acceptable_price_bps (plus optional min_fill_quantity); the unfilled remainder is cancelled, never rests |
Related docs#
- Placing Orders — user-facing guide to the order form and fill preview
- Order Book — matching rules, tick rounding, the canonical YES book
- Maker Price Band — where resting orders are allowed to post
- SDK: Reading Data — calling the estimator from your own code