How the market maker works

The maker behind every market: plain LMSR at a depth b. Every order fills in the transaction that sends it, at the price it leaves behind. Drag the sliders to see how an order moves prices, what it costs, when its limit stops it, and how depth grows as the market trades.

The cost function

Every market here is priced by one maker: a plain logarithmic market scoring rule (LMSR) at a depth b that only steps up between trades. The maker holds a cost function, and the price of each outcome is its slope.

C(q) = b · ln Σ_i exp(q_i / b)
p_i  = exp(q_i / b) / Σ_j exp(q_j / b)

The prices are a softmax, so Σ p_i = 1 exactly. There is no overround and no hidden spread. That exactness is what lets split and merge stay open while a market trades: a complete set is always worth exactly 1. Buying s shares of outcome i moves q_i → q_i + s, which raises p_i and lowers every other leg, because they must still sum to 1.

Read b as depth. The maker's worst-case loss is bounded by b · ln(1/p_min), so a larger b is a promise backed by more collateral, and the flip side is that a given order moves the price less as b grows. The two playgrounds below let you feel both halves: first how an order fills and what it costs, then how b deepens and the same order gets cheaper.

How an order fills

An order names an outcome, a side and an exact number of shares, on a buy and on a sell alike. Its limit is a marginal price: the most you will let the price reach on a buy, the least on a sell, or 0 for none. The order fills against the maker inside the transaction that submits it, or the transaction reverts. There is nothing to wait for and nothing to cancel.

d      = +shares on a buy, −shares on a sell
1. b      = ratchet(pre-trade state)       // monotone up, fee-funded, capped per outcome
2. pAfter = p_i(q + d·e_i, b)              // the post-fill marginal price, quoted once
3. limit  : buy reverts if pAfter > limit, sell reverts if pAfter < limit   (LimitBreached)
4. cash   = ceil(shares · pAfter)  on a buy
          = floor(shares · pAfter) on a sell
5. fee    = ceil(cash · feeBps / 10,000)   // protocol + creator + liquidity slices
6. check  : locked ≥ claims(i) for every outcome after the fill, else revert (Insolvent)
7. buy    : pay cash + fee, receive the shares
   sell   : burn the shares, receive cash − fee

Because the share count is fixed before any price is computed, the post-fill state is known up front and the order is priced with one evaluation of the curve. Rounding always favours the pool: up on what a buyer pays, down on what a seller receives. A zero quote reverts, and so does a sell whose cash rounds down to zero, so shares are never minted against nothing or burned for nothing. The solvency check in step 6 is exact and runs on every fill.

previewOrder returns the exact cash, fee and post-fill price a fill would produce at the current state. The trade ticket shows those numbers and sets the limit from the post-fill price plus a tolerance (1% by default), so a trade that lands just before yours does not revert it, and a larger move does.

Playground A: one order

synthetic · no chain
Outcomes
Starting prices (drag; they renormalise to 100%)
Traded outcome
Price vector: the ghost bar is before, the solid bar is after
Yes
62.2%+12.2
No
37.8%-12.2
after-vector sum 1.000000
Post-fill Yes price
62.25%
50.00% before
Price impact
+12.25 pts
Shares bought
500
You pay$314.34
cash
ceil(500 × 62.25¢) = $311.23
fee
ceil(cash × 1.00%) = $3.11
total
cash + fee = $314.34
No limit set: this order has no protection if the price moves before it lands.

Every share fills at the post-fill price, the price the order leaves behind. A buy pays the higher end of the move and a sell receives the lower end, which keeps the pool at least as well covered as the curve and stops a trader from growing the depth ratchet with round trips. Cash and fee use the trade ticket's own functions, so these are the ticket's numbers for the same curve. The fee is all slices combined (protocol, creator and liquidity), 1% here.

Post-fill Yes price as you buy more, at three depthsb/4 = 250b = 1,0004b = 4,000
0%50%100%02000500shares bought
A deeper b flattens the curve: the same order moves the price less.

Playground B: depth deepens

the b ratchet
bTarget = liquidityFees / ln(n) + alpha · netSharesSold
bAfford = min_i (locked − claims(i)) / ln(1/p_i)
bGrow   = b_old + min_i (locked − claims(i) − b_old·ln(1/p_i)) / ln(n)   // b_old > 0 only
b_new   = max(b_old, min(bTarget, bAfford, bGrow))   // monotone up, never down
Outcomes
Current b
2,028
$100 order moves
2.35 pts
target ≤ 3 pts
Depth gate bRequired
1,667
cleared
b over accrued volumebaffordable ceilinggate
01,4192,839$0k$100kbRequired 1,667cumulative volume
b only ratchets up. It grows as liquidity fees accrue and is capped by the affordable ceiling, the collateral that actually exists. Here b clears the gate at about $15,333 of volume. The 3-point bound and bRequired are the two-outcome derivation (Δp ≈ order/(2b) at p = ½); with more outcomes the same b clears the gate but a $100 order still moves a little more, which is why the move is coloured against 3 points directly.

Honest simplifications for this toy: a uniform book (so ln(1/p_i) = ln n on every leg), net maker inventory modelled as 2% of cumulative volume, and 25% of backing collateral treated as the reserve slice that funds the opening depth. On-chain, bAfford is exact and per-outcome, locked is the real deposit pool plus reserve plus fees, and claims(i) also counts auction deposits that have not been converted yet.

What the limit protects

Transactions in a block execute in sequence, so an order can be moved against up to its limit price. The limit is the protection, which is why every client sets one: past it, the order reverts and none of your money moves.

More on the protocol in the resources.