Market Making#
Market makers provide two-sided liquidity — they sit on both sides of the book simultaneously, earning the bid-ask spread when both sides fill. As a maker, you pay zero fee on every fill. Your P&L comes purely from the spread you quote.
How the spread works#
Bid @ 5400 bps | Ask @ 5600 bps
Spread = 5600 − 5400 = 200 bps (2%)
When both sides fill:
- Buy 100 shares @ 5400 bps = pay 54 USDT
- Sell 100 shares @ 5600 bps = receive 56 USDT
- Gross profit = 2 USDT (no fees, you're the maker both times)
The taker on each fill pays the fee — you don't. The taker fee is split 50/10/40 among protocol treasury / market creator / referrer. None of it comes from you.
Post-Only orders: always be the maker#
Use the Post-Only order type to ensure you are never the taker:
| Order type | If would match | Result |
|---|---|---|
| Limit | Matches immediately | You become taker (pay fee) |
| Post-Only | Rejects with WouldCross | No trade (no fee risk) |
A Post-Only order either rests at your price or is rejected — it never crosses. This is the right order type for market-making strategies.
The PlaceMultiplePostOnlyOrders (0x0C) instruction lets you place up to
8 post-only orders atomically in a single transaction, which reduces the
number of CPI calls needed to update quotes.
The maker price band#
All resting orders (Limit and Post-Only) are gated against the maker price band. IOC orders are exempt because they never rest.
Two-sided book (both bids and asks exist):
mid = (best_bid + best_ask) / 2
band = [max(100, mid − 1000), min(9900, mid + 1000)]
One-sided or empty book:
band = [100, 9900] (static fallback)
Constants (from src/constants.rs):
| Constant | Value | Meaning |
|---|---|---|
MAKER_BAND_BPS | 1000 | ±10% around mid |
STATIC_MIN_BPS_FOR_BAND | 100 | 1% dust floor |
STATIC_MAX_BPS_FOR_BAND | 9900 | 99% dust ceiling |
Example with mid at 5500 bps: your order must be in [4500, 6500]. Orders
outside this range are rejected with OrderPriceOutOfBand — nothing is
lost, just re-quote closer to the market.
Practical impact for makers: if the mid moves while your update transaction is in flight, your new quotes may land outside the band and be rejected. Design your update loop to handle this gracefully.
Minimum resting notional#
Each resting order must meet a minimum notional value (the stablecoin
amount locked). The default DEFAULT_MIN_RESTING_NOTIONAL is 2 base
units (effectively dust protection for 6-decimal USDT). This prevents
tiny junk orders from consuming order-book slots.
Orders below the minimum are rejected at placement. When a large order partially fills and the remaining quantity would fall below the minimum, the remainder is cancelled rather than left as a sub-minimum resting stub.
Order book capacity#
Each side of each market holds at most 63 resting orders. When the
side is full, new orders are rejected with OrderbookFull. For market
makers, this means:
- Keep your resting order count well below 63 per side.
- Cancel stale quotes promptly to free slots.
- If you need atomic multi-order updates, use the batch post-only instruction.
Basic two-sided quoting strategy#
Example quote calculation (tick = 100 bps)#
Note that tick rounding happens after the NO-side conversion for any NO-side orders. Always verify your canonical price after rounding.
Inventory management#
Why inventory matters#
When your bid fills, you own YES shares. When your ask fills, you've sold YES shares. Over time, imbalanced fills accumulate directional exposure — if your bids fill faster than your asks (a falling market), you build a long YES position that loses value.
Skewing quotes to reduce inventory#
Adjust your mid price based on current inventory:
| Inventory | Skew direction | Effect |
|---|---|---|
| Long YES (too many) | Lower mid → lower bid + ask | Makes selling YES more attractive |
| Long NO (too many) | Raise mid → higher bid + ask | Makes buying YES more attractive |
| Neutral | No skew | Symmetric quotes |
inventory_skew_bps = delta_shares × skew_factor_bps_per_share
adjusted_mid = raw_mid − inventory_skew_bps
Delta exposure#
delta = yes_shares − no_shares
delta > 0 → long YES, profits if market resolves UP
delta < 0 → long NO, profits if market resolves DOWN
delta = 0 → neutral, profits from spread only
Risk management#
Time risk#
Seesaw markets have a fixed end time. Within the final minutes, a market
maker's position is exposed to binary resolution risk — whatever inventory
you hold settles at $0 or $1. Most strategies cancel all orders before
t_end and flat out remaining inventory.
Hard limits#
| Metric | Suggested limit | Action when exceeded |
|---|---|---|
| Max absolute YES | 500 shares | Stop posting bids |
| Max absolute NO | 500 shares | Stop posting asks |
| Net delta | ±200 shares | Aggressive skew |
| Time remaining | < 2 min | Cancel all, exit |
Common pitfalls#
| Pitfall | Consequence | Prevention |
|---|---|---|
| Inventory build-up | Large directional loss at resolution | Aggressive skewing; delta limits |
| Slow quote updates | Stale quotes → adverse selection | Fast execution; use WebSocket subscriptions |
| Ignoring time | Trading into resolution with large inventory | Time-based cutoff rules |
| Tight spread at extremes | Negative EV after taker fee slippage | Check effective maker EV including costs |
| Over-posting | Fill 63 slots, can't update | Keep order count headroom |
Fee economics#
Makers pay zero fee. Takers pay the capped-decay fee:
fee_bps(price) = min(200, 600 × (10000 − price) / 10000)
As a maker, your EV on a round-trip is:
EV = spread_bps × quantity / 10000 (no fee subtracted)
The only fee you pay is when you must exit a position as a taker (e.g.,
closing inventory before t_end). Factor the taker fee into your minimum
spread when sizing exits.
Automation requirements#
Effective market making is hard to do manually. See Automation for implementation details including:
- Reading the order book via Solana RPC or WebSocket
- Building and sending
PlaceMultiplePostOnlyOrdersbatches - Tracking fills and adjusting quotes programmatically
Market making checklist#
Before starting
- Understand the asset and typical spread
- Set maximum inventory limits (YES and NO separately)
- Define minimum acceptable spread
- Decide on time-based exit cutoff (e.g., cancel all at t_end − 2 min)
During trading
- Monitor fills and inventory after each transaction
- Re-quote after fills using the latest book state
- Watch the price band — reject rejections gracefully
- Track unrealized inventory exposure
Before market end
- Cancel all resting orders before
t_end - Flatten remaining inventory via IOC or limit sells/buys
- Ensure no unfilled resting buy orders remain past resolution (they must be redeemed within 7 days — see Risks)
- Claim any accumulated creator fees if you also created the market
Next steps#
- Automation — running a maker programmatically with permissionless cranks and free-funds flows
- Placing Orders — all order types and parameters
- Risks — the 7-day rule and market-admin controls
Technical reference#
| Mechanism | Instruction |
|---|---|
| Single post-only order | PlaceOrder (0x0B) with order_type = PostOnly |
| Batch post-only orders | PlaceMultiplePostOnlyOrders (0x0C) |
| Batch post-only (free funds) | PlaceMultiplePostOnlyOrdersWithFreeFunds (0x0F) |
| Cancel by IDs | CancelMultipleOrdersById (0x11) |
| Cancel all | CancelAllOrders (0x12) |
| Cancel above/below price | CancelUpTo (0x13) |
| Post-only mode status | SetMarketEmergencyStatus (0x2C) / EnablePostOnlyMode (0x27) |