Skip to content

AMM & pricing

Core mechanism

EventMarket uses a constant-product market maker (CPMM) — the same model as Uniswap v2 — over two virtual token reserves: yesReserve and noReserve.

yesReserve × noReserve = k
yesReserve × noReserve = kyesReservenoReserveprice
Trades slide the reserves along the curve; k only grows as swap fees accrue. The price of YES is its share of the pool — noReserve / (yesReserve + noReserve).

The invariant k grows over time as swap fees accumulate, which is how LPs earn yield.

Implied probability

Because 1 YES + 1 NO always equals 1 USDC, the fair price of YES is its "buying power" fraction of the pool. When the NO reserve is large, more collateral sits on the NO side — meaning YES is expensive, i.e. more likely to win:

P(YES) = noReserve  / (yesReserve + noReserve)
P(NO)  = yesReserve / (yesReserve + noReserve)

Collateral invariant

Every outcome token is fully collateralized. Depositing n USDC mints exactly n YES and n NO. The contract maintains two tight identities throughout its lifetime:

yesReserve + Σ yesBalanceOf[u]  =  totalCollateral
noReserve  + Σ noBalanceOf[u]   =  totalCollateral

Total YES outstanding always equals total NO outstanding equals totalCollateral.


Swap fee

Every trade charges lpSwapFeeBps (e.g. 100 = 1 %). The fee is taken on the input side before the CPMM output is calculated. The full input still enters the reserve, so k grows:

effectiveIn = amountIn × (10 000 − lpSwapFeeBps) / 10 000

The output is computed on effectiveIn while the full amountIn sits in the reserve — the shortfall is pure value added to the pool, accruing proportionally to all LP shares.


End-to-end walkthrough

The six steps below form one connected example. Contract parameters: lpSwapFeeBps = 100 (1 %), USDC has 6 decimals (1 USDC = 1 000 000 raw tokens).

State summary

StepyesReservenoReservetotalCollateralP(YES)
0. Start000
1. initLiquidity 10 USDC10.00000010.00000010.00000050.00 %
2. addLiquidity 10 USDC20.00000020.00000020.00000050.00 %
3. buyYes 1 USDC19.05669421.00000021.00000052.42 %
4. buyNo 1 USDC20.05669419.96292122.00000049.88 %
5. sellYes21.05669419.02390021.06097947.46 %
6. sellNo20.01511220.02390020.02390049.99 %

Step 1 — initializeMarket(10 USDC)

The factory deposits the entire initLiquidity into the AMM pool as locked Initiator LP. No tokens are purchased; the pool opens perfectly balanced.

_addLiquidity (bootstrap, totalLpShares == 0):

shares     = 10 000 000
yesReserve = 10 000 000
noReserve  = 10 000 000
totalCollateral   = 10 000 000
lpShares[creator] = 10 000 000  (locked)
P(YES) = 10 000 000 / (10 000 000 + 10 000 000) = 50.00 %

The market opens at a neutral 50 / 50 price. Price discovery is left entirely to traders who buy YES or NO after launch.


Step 2 — addLiquidity(10 USDC)

Symmetric injection: both reserves increase by the same amount.

shares = 10 000 000 × 10 000 000 / 10 000 000 = 10 000 000

yesReserve    += 10 000 000  →  20 000 000
noReserve     += 10 000 000  →  20 000 000
totalCollateral += 10 000 000 →  20 000 000
totalLpShares   += 10 000 000 →  20 000 000
P(YES) = 20 000 000 / (20 000 000 + 20 000 000) = 50.00 %

Because both reserves grow equally, adding liquidity to an already-balanced pool leaves the price unchanged. The deeper pool reduces the price impact of each future trade.


buyYes

Contract function: buyYes(usdcAmount, minYesOut)

Mechanism (_buyYes):

  1. totalCollateral += usdcAmount
  2. yesBalanceOf[user] += usdcAmount — the minted YES goes straight to the user.
  3. Route the matching newly-minted NO through the AMM (_swapNoForYes):
    • effectiveIn = usdcAmount × (10 000 − fee) / 10 000
    • swappedYes = yesReserve × effectiveIn / (noReserve + effectiveIn)
    • noReserve += usdcAmount (full amount enters the reserve)
    • yesReserve -= swappedYes
  4. yesBalanceOf[user] += swappedYes
  5. Return yesOut = usdcAmount + swappedYes

Step 3 — buyYes(1 USDC)

Current state: yesReserve = 20 000 000, noReserve = 20 000 000

effectiveIn = 1 000 000 × 9 900 / 10 000 = 990 000

swappedYes = 20 000 000 × 990 000 / (20 000 000 + 990 000)
           = 19 800 000 000 000 / 20 990 000
           = 943 306

noReserve  += 1 000 000  →  21 000 000
yesReserve -= 943 306    →  19 056 694
totalCollateral += 1 000 000  →  21 000 000

yesOut = 1 000 000 + 943 306 = 1 943 306 YES
P(YES): 50.00 % → 52.42 %

Collateral invariant check:

yesBalanceOf[buyer](1 943 306) + yesReserve(19 056 694)
= 21 000 000 = totalCollateral  ✓

Average fill price: 1 USDC / 1.943306 YES ≈ 0.515 USDC per YES (includes price impact + 1 % fee).


buyNo

Contract function: buyNo(usdcAmount, minNoOut)

Symmetric to buyYes. The minted YES enters the pool; the pool pays out extra NO.

Step 4 — buyNo(1 USDC)

Current state: yesReserve = 19 056 694, noReserve = 21 000 000

effectiveIn = 1 000 000 × 9 900 / 10 000 = 990 000

swappedNo = 21 000 000 × 990 000 / (19 056 694 + 990 000)
          = 20 790 000 000 000 / 20 046 694
          = 1 037 079

yesReserve += 1 000 000  →  20 056 694
noReserve  -= 1 037 079  →  19 962 921
totalCollateral += 1 000 000  →  22 000 000

noOut = 1 000 000 + 1 037 079 = 2 037 079 NO
P(YES): 52.42 % → 49.88 %

YES had just been pushed up by step 3, so NO is now slightly underpriced (P(NO) ≈ 50.12 %). The buyer receives a little more than 2 NO per USDC.


sellYes

Contract function: sellYes(yesAmount, minUsdcOut)

Selling YES requires a two-step manoeuvre because the pool holds YES and NO — not USDC directly:

  1. Exit swap: route s YES into the pool to receive NO.
  2. Pair redeem: burn the remaining (yesAmount − s) YES together with the received NO, 1:1 into USDC.

The goal is to choose s such that yesAmount − s ≈ noOut, so both sides cancel completely and USDC out is maximised.

Exit-swap quadratic

Setting yesLeft = yesAmount − s equal to noOut = _calcYesForNo(s, ...) and letting φ = 10 000 − lpSwapFeeBps:

φ·s²  +  (yesReserve·BPS + noReserve·φ − yesAmount·φ)·s  −  yesAmount·yesReserve·BPS  =  0

Positive root (from _solveExitSwap):

b = yesReserve·BPS + noReserve·φ − yesAmount·φ

When b ≥ 0:
       √(b² + 4φ · yesAmount · yesReserve · BPS) − b
s  =  ─────────────────────────────────────────────


When b < 0  (let b' = −b > 0):
       √(b'² + 4φ · yesAmount · yesReserve · BPS) + b'
s  =  ──────────────────────────────────────────────

Step 5 — sellYes(1 943 306 YES)

Current state: yesReserve = 20 056 694, noReserve = 19 962 921, φ = 9 900

Part A — solve for s:

ab = reserveIn·BPS + reserveOut·φ
   = 20 056 694 × 10 000 + 19 962 921 × 9 900
   = 200 566 940 000 + 197 632 917 900
   = 398 199 857 900

c  = amount·φ = 1 943 306 × 9 900 = 19 238 729 400

ab > c  →  b = 398 199 857 900 − 19 238 729 400 = 378 961 128 500

C        = amount × reserveIn × BPS
         = 1 943 306 × 20 056 694 × 10 000 ≈ 3.896 × 10¹⁷

4φC      = 4 × 9 900 × 3.896 × 10¹⁷ ≈ 1.543 × 10²²

b²       = (3.790 × 10¹¹)² ≈ 1.436 × 10²³

Δ ≈ 1.590 × 10²³

√Δ ≈ 3.988 × 10¹¹

s = (√Δ − b) / (2φ) ≈ 1 000 000 raw

Part B — NO received from the swap:

_calcYesForNo(s = 1 000 000, yesReserve = 20 056 694, noReserve = 19 962 921):

effectiveS = 1 000 000 × 9 900 / 10 000 = 990 000

noOut = 19 962 921 × 990 000 / (20 056 694 + 990 000)
      = 19 763 ··· / 21 046 694
      ≈ 939 021

Part C — pair redeem:

yesReserve += 1 000 000  →  21 056 694
noReserve  -= 939 021    →  19 023 900

yesLeft  = 1 943 306 − 1 000 000 = 943 306
pair     = min(943 306, 939 021) = 939 021

totalCollateral -= 939 021  →  21 060 979
USDC out = 939 021 raw = 0.939021 USDC

yesBalanceOf[seller] -= (1 000 000 + 939 021) → 4 285 dust remaining
P(YES): 49.88 % → 47.46 %

buyYes → sellYes roundtrip loss: 1 USDC in, 0.9390 USDC back, −6.10 %.

Loss comes from two compounding 1 % swap fees plus the bid/ask spread. Between buy and sell, buyNo moved the market slightly against this position.


sellNo

Contract function: sellNo(noAmount, minUsdcOut)

Mirror of sellYes. Call _solveExitSwap with (noAmount, noReserve, yesReserve) (reserves swapped), route s NO into the pool to receive YES, then pair-redeem.

Step 6 — sellNo(2 037 079 NO)

Current state: yesReserve = 21 056 694, noReserve = 19 023 900, φ = 9 900

Part A — solve for s:

ab = 19 023 900 × 10 000 + 21 056 694 × 9 900
   = 190 239 000 000 + 208 461 270 600
   = 398 700 270 600

c  = 2 037 079 × 9 900 = 20 167 082 100

ab > c  →  b = 398 700 270 600 − 20 167 082 100 = 378 533 188 500

C        = 2 037 079 × 19 023 900 × 10 000 ≈ 3.876 × 10¹⁷

4φC      ≈ 1.535 × 10²²

b²       ≈ 1.433 × 10²³

Δ ≈ 1.586 × 10²³

√Δ ≈ 3.983 × 10¹¹

s ≈ 1 000 000 raw

Part B — YES received from the swap:

_calcNoForYes(s = 1 000 000, yesReserve = 21 056 694, noReserve = 19 023 900):

effectiveS = 1 000 000 × 9 900 / 10 000 = 990 000

yesOut = 21 056 694 × 990 000 / (19 023 900 + 990 000)
       = 20 846 ··· / 20 013 900
       ≈ 1 041 582

Part C — pair redeem:

noReserve  += 1 000 000  →  20 023 900
yesReserve -= 1 041 582  →  20 015 112

noLeft   = 2 037 079 − 1 000 000 = 1 037 079
pair     = min(1 037 079, 1 041 582) = 1 037 079

totalCollateral -= 1 037 079  →  20 023 900
USDC out = 1 037 079 raw = 1.037079 USDC

noBalanceOf[seller] → 4 503 dust remaining
P(YES): 47.46 % → 49.99 %

buyNo → sellNo roundtrip gain: 1 USDC in, 1.0371 USDC back, +3.71 %.

Profit arises because NO was bought when YES was slightly overpriced (P(YES) = 52.42 %, so P(NO) = 47.58 %) and sold after the price recovered. The ~4.5 % pricing edge exceeds the two 1 % swap fees.


Six-step P&L summary

OperationUSDC spentTokens receivedUSDC returnedNet
initLiquidity 1010.00000010 M LP (locked)
addLiquidity 1010.00000010 M LP shares
buyYes 11.0000001.943306 YES
buyNo 11.0000002.037079 NO
sellYes0.939021−0.060979
sellNo1.037079+0.037079

LP fee income (accrued inside the pool):

totalCollateral after step 2:     20.000000 USDC
injected by buyYes + buyNo:       +2.000000
withdrawn by sellYes + sellNo:    −1.976100

final totalCollateral:            20.023900 USDC
net fee income for LPs:            0.023900 USDC  ≈ 0.120 % across 4 trades

On-chain quotes

FunctionReturns
quoteYes(usdcAmount)YES tokens out for a given USDC buy
quoteNo(usdcAmount)NO tokens out for a given USDC buy
quoteSellYes(yesAmount)USDC out for selling yesAmount YES
quoteSellNo(noAmount)USDC out for selling noAmount NO
yesProbability()Current P(YES) as a 1e18-scaled fraction
noProbability()Current P(NO) as a 1e18-scaled fraction

All reads are pure view calls — free to call at any time. Every buy* / sell* function accepts a minOut parameter for slippage protection; the transaction reverts if the AMM quote moves adversely before execution.

Onchain prediction markets, priced by an AMM and settled in USDC.