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 = kk 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] = totalCollateralTotal 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 000The 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
| Step | yesReserve | noReserve | totalCollateral | P(YES) |
|---|---|---|---|---|
| 0. Start | 0 | 0 | 0 | — |
| 1. initLiquidity 10 USDC | 10.000000 | 10.000000 | 10.000000 | 50.00 % |
| 2. addLiquidity 10 USDC | 20.000000 | 20.000000 | 20.000000 | 50.00 % |
| 3. buyYes 1 USDC | 19.056694 | 21.000000 | 21.000000 | 52.42 % |
| 4. buyNo 1 USDC | 20.056694 | 19.962921 | 22.000000 | 49.88 % |
| 5. sellYes | 21.056694 | 19.023900 | 21.060979 | 47.46 % |
| 6. sellNo | 20.015112 | 20.023900 | 20.023900 | 49.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 000P(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):
totalCollateral += usdcAmountyesBalanceOf[user] += usdcAmount— the minted YES goes straight to the user.- Route the matching newly-minted NO through the AMM (
_swapNoForYes):effectiveIn = usdcAmount × (10 000 − fee) / 10 000swappedYes = yesReserve × effectiveIn / (noReserve + effectiveIn)noReserve += usdcAmount(full amount enters the reserve)yesReserve -= swappedYes
yesBalanceOf[user] += swappedYes- 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 YESP(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 NOP(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:
- Exit swap: route
sYES into the pool to receive NO. - 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 = 0Positive root (from _solveExitSwap):
b = yesReserve·BPS + noReserve·φ − yesAmount·φ
When b ≥ 0:
√(b² + 4φ · yesAmount · yesReserve · BPS) − b
s = ─────────────────────────────────────────────
2φ
When b < 0 (let b' = −b > 0):
√(b'² + 4φ · yesAmount · yesReserve · BPS) + b'
s = ──────────────────────────────────────────────
2φ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 rawPart 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 021Part 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 remainingP(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 rawPart 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 582Part 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 remainingP(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
| Operation | USDC spent | Tokens received | USDC returned | Net |
|---|---|---|---|---|
| initLiquidity 10 | 10.000000 | 10 M LP (locked) | — | — |
| addLiquidity 10 | 10.000000 | 10 M LP shares | — | — |
| buyYes 1 | 1.000000 | 1.943306 YES | — | — |
| buyNo 1 | 1.000000 | 2.037079 NO | — | — |
| sellYes | — | — | 0.939021 | −0.060979 |
| sellNo | — | — | 1.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 tradesOn-chain quotes
| Function | Returns |
|---|---|
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.