Implementation of the Paradigm pm-AMM on Solana.
pm-AMM — Paradigm Dynamic AMM for Prediction Markets on Solana
First production implementation of the Paradigm pm-AMM on Solana. 100% fidelity to the paper. Uniform LVR in price and time.
Based on pm-AMM: A Prediction Market AMM by Ciamac Moallemi & Dan Robinson (Paradigm, Nov 2024).
Program ID: 8V872cTKfH1gC5zBvQhrQN2DXSmRNokPPjPsBE46MZNj (Devnet Explorer)
The Math
The dynamic pm-AMM invariant (paper section 8):
(y - x) Phi((y - x) / Leff) + Leff phi((y - x) / L_eff) - y = 0
Where Leff = L0 * sqrt(T - t) decreases over time, and phi/Phi are the standard normal PDF/CDF.
Three properties proven by the paper, verified on-chain:
| Property | Formula | Our test result | |---|---|---| | Uniform LVR (price-independent) | LVRt = Vt / (2*(T-t)) | Std across 7 prices: 0.000% | | Constant E[LVR] (time-independent) | E[LVRt] = V0 / (2T) | Linearity ratio: 0.994 (500 MC runs) | | LP wealth at expiry | E[WT] = W0 / 2 | Measured: 0.518 (500 MC runs, 5% tolerance) |
The dC_t Mechanism — Why This is Different
Traditional AMMs leave LPs fully exposed until they withdraw. The pm-AMM actively redistributes liquidity to LPs over time:
deposit 1000 USDC claim YES+NO redeem for USDC
| | |
v v v
|---------|---------|---------|---------|---------|
t=0 t=1d t=2d t=3d ... T
| | |
v v v
dCt accrual: tokens released as Leff decreases
As time passes, Leff = L0 * sqrt(T-t) shrinks. The reserves scale proportionally, releasing YES+NO tokens to LPs via per-share accumulators. LPs can:
- Claim YES+NO tokens at any time
- Redeem 1 YES + 1 NO = 1 USDC (pair redemption)
- Sell on the pool via swap
- Hold until resolution for the winning side
Conservation verified:
At fixed price (no arbitrage), 100% of pool value returns to LPs. With random walks (Gaussian score dynamics), exactly 50% returns (the other 50% is LVR consumed by arbitrageurs). Both verified in our test suite.
Architecture
pm-amm/
anchor/ # Solana program (Anchor/Rust)
programs/pm_amm/src/
pmmath.rs # Fixed-point math (phi, Phi, Phiinv, reserves, swap)
accrual.rs # dCt mechanism (compute, apply, accruefirst)
lut.rs # 2048-point lookup tables for on-chain perf
state.rs # Market, LpPosition structs
errors.rs # Error codes
instructions/ # 11 instructions (fully-backed architecture, Sprint 20)
tests/ # 18 TS integration tests
scripts/ # Deploy + seed scripts
app/ # Next.js frontend
oracle/ # Python truth oracle (scipy reference)
doc/ # Paper reference, PRD, sprint definitions
11 Instructions
| Instruction | Who | Description | |---|---|---| | initialize_market | Anyone | Create market with YES/NO mints, USDC vault, pool ATAs | | depositliquidity | LP | Add USDC, get shares. Bootstraps L0 on first deposit | | mint_pair | Anyone | Burn 1 USDC → mint 1 YES + 1 NO (Sprint 20, fully-backed) | | redeem_pair | Holder | Burn 1 YES + 1 NO → 1 USDC | | swapyesno | Trader | Pure YES↔NO swap on the pm-AMM curve (2 directions) | | withdraw_liquidity | LP | Burn shares, receive YES+NO proportional | | accrue | Anyone | Permissionless dC_t accrual (keeper) | | claimlpresiduals | LP | Claim accrued YES+NO tokens | | resolve_market | Authority | Set winning side after expiration | | claim_winnings | Holder | Burn winning tokens for 1 USDC each | | suggestlzero | Anyone | View: compute optimal L_0 for a budget |
USDC ↔ YES/NO trades are built client-side as atomic instruction combos:
- BUY YES/NO =
mintpair(δ)+swapyes_no(dump the unwanted side) - SELL YES/NO =
swapyesno(rebalance to a pair) +redeem_pair(δ)
vault.usdc == yesmint.supply == nomint.supply at all times. Fully-backed outcome tokens, Polymarket-style — no structural rug possible.
Robustness Beyond the Gaussian Model
| Test | Setup | Result | |---|---|---| | Jump (deterministic) | P=0.5 -> P=0.87 in one swap | Invariant: 0.00e+00, no overflow | | MC with jumps | 200 runs, 20% jump probability | LVR -35.8% vs Gaussian (lower because jumps push prices to extremes where V is lower) | | 100 random swaps | Alternating directions, random sizes | Max invariant: 9e-13 |
Composability
All accounts are deterministic PDAs:
// Derive all addresses from market_id alone
const [market] = PublicKey.findProgramAddressSync(
[Buffer.from("market"), marketId.toArrayLike(Buffer, "le", 8)],
PROGRAM_ID
);
const [yesMint] = PublicKey.findProgramAddressSync(
[Buffer.from("yesmint"), market.toBuffer()], PROGRAMID
);
// ... same for no_mint, vault
suggestlzero is callable via CPI for auto-LP vaults:
await program.methods
.suggestLZero(budgetUsdc, sigmaBps)
.accounts({ market })
.rpc();
// Emits LZeroSuggestion event with suggestedlzero, daily_lvr, warnings
IDL
The Anchor IDL is available at idl/pm_amm.json for integrators building on top of pm-AMM.
Test Suite
216 tests total:
| Category | Count | Coverage | |---|---|---| | Rust unit tests (pm_math, accrual, state) | 62 | All math functions, Q64.64 roundtrips, accrual properties, solver precision | | TS integration tests | 18 | Full lifecycle: init -> deposit -> mintpair -> swapyes_no -> claim -> resolve | | Python property tests | 24 | Paradigm properties A/B/C, robustness D/E/F, initial-price G | | Python oracle tests | 112 | Cross-validation against scipy |
Run with:
pnpm run test:rust # Rust unit (62)
pnpm run test # TS integration on localnet (18)
pnpm run test:all # Rust + Python (skips TS)
python3 oracle/testoracle.py && python3 oracle/testproperties.py # Python (136)
Known Limitations
- Oracle: admin-only resolution (no oracle integration)
- Binary only: YES/NO outcomes, no multi-outcome
- 0% fees: no trading fees (pure LVR model)
Roadmap
- [ ] Oracle integration (Switchboard/Pyth for auto-resolution)
- [ ] Multi-outcome markets (categorical pm-AMM)
- [ ] Trading fees (LP incentive beyond dC_t)
- [ ] Delta hedging tools for sophisticated LPs
Prerequisites
- Rust (stable)
- Solana CLI (v3+)
- Anchor CLI (v1.0+)
- Node.js (v20+)
- pnpm (v9+)
- Python 3.10+ (for oracle tests only)
Quick Start
# Install dependencies
pnpm install
Build the program (anchor build + idl build)
pnpm run build
Run Rust unit tests (62 tests)
pnpm run test:rust
Run integration tests (18 tests, requires local validator)
pnpm run test
Run Python oracle + property tests (136 tests)
cd oracle && python3 testoracle.py && python3 testproperties.py
Run everything except TS integration
pnpm run test:all
Start the frontend
pnpm run dev
Deploy to devnet
pnpm run deploy
Environment Variables
Copy .env.example to .env.local in the app/ directory:
NEXTPUBLICRPC_URL=https://api.devnet.solana.com
KVRESTAPI_URL= # Upstash Redis (optional, for price history)
KVRESTAPI_TOKEN= # Upstash Redis (optional)
MINTAUTHORITYKEY= # Base64-encoded keypair for mock USDC faucet
Contributing
See CONTRIBUTING.md for setup, code standards, and PR guidelines.
License
Built for the $PREDICT hackathon by @matt.
Paper: Paradigm pm-AMM (Moallemi & Robinson, Nov 2024)