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 dynamic pm-AMM invariant (paper section 8):
(y - x) * Phi((y - x) / L_eff) + L_eff * phi((y - x) / L_eff) - y = 0
Where L_eff = L_0 * sqrt(T - t) decreases over time, and phi/Phi are the standard normal PDF/CDF.
| Property | Formula | Our test result |
|---|---|---|
| Uniform LVR (price-independent) | LVR_t = V_t / (2*(T-t)) |
Std across 7 prices: 0.000% |
| Constant E[LVR] (time-independent) | E[LVR_t] = V_0 / (2T) |
Linearity ratio: 0.994 (500 MC runs) |
| LP wealth at expiry | E[W_T] = W_0 / 2 |
Measured: 0.518 (500 MC runs, 5% tolerance) |
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
dC_t accrual: tokens released as L_eff decreases
As time passes, L_eff = L_0 * 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
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.
pm-amm/
anchor/ # Solana program (Anchor/Rust)
programs/pm_amm/src/
pm_math.rs # Fixed-point math (phi, Phi, Phi_inv, reserves, swap)
accrual.rs # dC_t mechanism (compute, apply, accrue_first)
lut.rs # 2048-point lookup tables for on-chain perf
state.rs # Market, LpPosition structs
errors.rs # Error codes
instructions/ # 10 instructions
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
| Instruction | Who | Description |
|---|---|---|
initialize_market |
Anyone | Create market with YES/NO mints, USDC vault |
deposit_liquidity |
LP | Add USDC, get shares. Bootstraps L_0 on first deposit |
swap |
Trader | 6 directions: USDC/YES/NO combinations |
withdraw_liquidity |
LP | Burn shares, receive YES+NO proportional |
accrue |
Anyone | Permissionless dC_t accrual (keeper) |
claim_lp_residuals |
LP | Claim accrued YES+NO tokens |
redeem_pair |
Holder | Burn 1 YES + 1 NO = 1 USDC |
resolve_market |
Authority | Set winning side after expiration |
claim_winnings |
Holder | Burn winning tokens for 1 USDC each |
suggest_l_zero |
Anyone | View: compute optimal L_0 for a budget |
| 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 |
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("yes_mint"), market.toBuffer()], PROGRAM_ID
);
// ... same for no_mint, vaultsuggest_l_zero is callable via CPI for auto-LP vaults:
await program.methods
.suggestLZero(budgetUsdc, sigmaBps)
.accounts({ market })
.rpc();
// Emits LZeroSuggestion event with suggested_l_zero, daily_lvr, warningsThe Anchor IDL is available at idl/pm_amm.json for integrators building on top of pm-AMM.
200 tests total:
| Category | Count | Coverage |
|---|---|---|
| Rust unit tests (pm_math, accrual, state) | 52 | All math functions, Q64.64 roundtrips, accrual properties, solver precision |
| TS integration tests | 18 | Full lifecycle: init -> deposit -> swap -> claim -> resolve |
| Python property tests | 18 | Paradigm properties A/B/C, robustness D/E/F |
| Python oracle tests | 112 | Cross-validation against scipy |
- Oracle: admin-only resolution (no oracle integration)
- Binary only: YES/NO outcomes, no multi-outcome
- 0% fees: no trading fees (pure LVR model)
- 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
- Rust (stable)
- Solana CLI (v3+)
- Anchor CLI (v1.0+)
- Node.js (v20+)
- pnpm (v9+)
- Python 3.10+ (for oracle tests only)
# Install dependencies
pnpm install
# Build the program
pnpm run build
# Run Rust unit tests (52 tests)
cd anchor && cargo test --package pm_amm
# Run integration tests (18 tests, requires local validator)
pnpm run test
# Run Python oracle tests (130 tests)
cd oracle && python3 test_oracle.py && python3 test_properties.py
# Start the frontend
pnpm run dev
# Deploy to devnet
pnpm run deployCopy .env.example to .env.local in the app/ directory:
NEXT_PUBLIC_RPC_URL=https://api.devnet.solana.com
KV_REST_API_URL= # Upstash Redis (optional, for price history)
KV_REST_API_TOKEN= # Upstash Redis (optional)
MINT_AUTHORITY_KEY= # Base64-encoded keypair for mock USDC faucetSee CONTRIBUTING.md for setup, code standards, and PR guidelines.
Built for the $PREDICT hackathon by @matt.
Paper: Paradigm pm-AMM (Moallemi & Robinson, Nov 2024)