A production-grade smart contract system for self-resolving subjective prediction/opinion markets using a bounded-loss LMSR (Logarithmic Market Scoring Rule) cost-function AMM.
This system enables binary prediction markets that self-resolve to their terminal probability distribution. Instead of requiring an external oracle to determine outcomes, markets terminate randomly after trades (with configurable probability α) and resolve to whatever probabilities the market has discovered at that moment.
- LMSR AMM: Bounded-loss automated market maker with mathematically guaranteed liquidity
- Self-Resolution: Markets resolve to terminal probabilities
r = q_currentupon random termination - VRF Integration: Secure randomness via Chainlink VRF v2.5 (or compatible) with rolling seed approach
- ERC20 Collateral: Support for arbitrary ERC20 tokens (USDC recommended)
- Fee System: Configurable trading fees with treasury accumulation
- Admin Controls: Pausable trading, configurable parameters, emergency resolution
┌─────────────────────────────────────────────────────────────┐
│ MarketFactory │
│ - Creates and registers markets │
│ - Manages allowed collateral tokens │
│ - Stores VRF seed for new market initialization │
└─────────────────────┬───────────────────────────────────────┘
│ creates
▼
┌─────────────────────────────────────────────────────────────┐
│ SelfResolvingMarket │
│ - Holds collateral │
│ - Tracks share inventory c[i] per outcome │
│ - Implements LMSR pricing (buy/sell) │
│ - Random termination check after trades │
│ - Claim settlement after resolution │
└─────────────────────────────────────────────────────────────┘
│
│ uses
▼
┌─────────────────────────────────────────────────────────────┐
│ FixedPointMath │
│ - UD60x18 fixed-point arithmetic │
│ - exp() and ln() implementations │
│ - LMSR cost function calculations │
└─────────────────────────────────────────────────────────────┘
For a binary market with shares outstanding c[0] and c[1]:
C(c₀, c₁) = b × ln(exp(c₀/b) + exp(c₁/b))
Where b is the liquidity parameter controlling price sensitivity.
q[i] = exp(c[i]/b) / Σⱼ exp(c[j]/b)
For a user buying δ shares of outcome 1:
amountIn = C(c₀, c₁ + δ) - C(c₀, c₁)
To calculate shares out for a given collateral input:
Let A = exp(c₁/b), B = exp(c₀/b)
δ = b × ln(((B + A) × exp(amountIn/b) - B) / A)
The system uses a "rolling seed" approach for randomness:
- VRF Seed: A
vrfSeedis maintained and updated periodically via VRF callback - Per-Trade Randomness: Each trade derives its random value from:
rand = keccak256(vrfSeed, tradeCount, msg.sender, block.timestamp, block.prevrandao) - Termination Check: If
rand % 1e18 < alpha, market resolves immediately - Resolution: Probabilities
r[i] = q[i]are frozen, trading disabled, claims enabled
- VRF seed is unpredictable at trade submission time
- Block parameters provide additional entropy
- Alpha is properly calibrated for desired market duration
MarketFactory.MarketParams memory params = MarketFactory.MarketParams({
collateral: USDC_ADDRESS,
collateralDecimals: 6,
b: 1000e18, // Liquidity parameter
alpha: 1e16, // 1% resolution chance per trade
feeBps: 100, // 1% fee
feeRecipient: treasury,
question: "Will ETH reach $10k in 2025?",
outcomes: ["YES", "NO"]
});
address market = factory.createMarket(params);// Approve collateral first
USDC.approve(market, amount);
// Buy shares
(uint256 sharesQuoted, uint256[2] memory newProbs) = market.quoteBuy(1, amount);
uint256 sharesOut = market.buy(1, amount, minShares, deadline);
// Sell shares
(uint256 amountQuoted, uint256[2] memory newProbs) = market.quoteSell(1, shares);
uint256 amountOut = market.sell(1, shares, minAmount, deadline);Markets resolve automatically after random termination. Once resolved:
// Check resolution
(bool resolved, , , uint256[2] memory r) = market.state();
// Calculate payout
uint256 payout = market.calculatePayout(user);
// Claim
uint256 received = market.claim();Each share of outcome i pays r[i] units of collateral:
payout = Σᵢ userShares[user][i] × r[i]
# Clone repository
git clone <repo-url>
cd prediction-market
# Install dependencies
forge install
# Build
forge build
# Test
forge test -vvv
# Test with gas reporting
forge test --gas-report# Run all tests
forge test
# Run specific test file
forge test --match-path test/SelfResolvingMarket.t.sol
# Run with verbosity
forge test -vvvv
# Run fuzz tests with more runs
forge test --fuzz-runs 10000- ✅ LMSR math correctness (exp, ln, cost function)
- ✅ Buy/sell price impact verification
- ✅ Quote-execution consistency
- ✅ Slippage protection
- ✅ Resolution state transitions
- ✅ Claim payout accuracy
- ✅ Fee accumulation and withdrawal
- ✅ Randomness integration (mock VRF)
- ✅ Invariants (no negative balances, probabilities sum to 1)
- Deploy or connect to Chainlink VRF v2.5 Coordinator
- Create and fund VRF subscription
- Have treasury address ready
- Have allowed collateral tokens ready
// 1. Deploy VRF Consumer
VRFConsumer vrfConsumer = new VRFConsumer(
vrfCoordinatorAddress,
subscriptionId,
keyHash,
callbackGasLimit,
requestConfirmations
);
// 2. Deploy Factory
MarketFactory factory = new MarketFactory(
treasury,
defaultFeeBps, // e.g., 100 for 1%
defaultAlpha // e.g., 1e16 for 1%
);
// 3. Configure
factory.setCollateralAllowed(USDC, true);
factory.setVrfCoordinator(address(vrfConsumer));
vrfConsumer.setFactory(address(factory));
// 4. Initial VRF seed
vrfConsumer.requestRandomness();
// Wait for callback...- ReentrancyGuard: All external functions protected
- SafeERC20: Safe token transfers
- Overflow Protection: Fixed-point math with bounds checking
- Probability Clamping: Epsilon bounds prevent log(0)
- Slippage Protection: minSharesOut/minAmountOut parameters
- Deadline Enforcement: Transaction expiry
- Single Resolution: Market can only resolve once
- Decimal Scaling: USDC (6 decimals) requires careful scaling to internal 1e18
- Randomness Quality: Rolling seed approach has weaker guarantees than per-trade VRF
- Gas Costs: exp/ln computations are gas-intensive
- MEV: Trades visible in mempool could be front-run
Before mainnet deployment:
- Professional security audit
- Economic modeling review
- VRF integration verification
- Gas optimization analysis
| Parameter | Description | Recommended Range |
|---|---|---|
b |
Liquidity parameter | 100-10000 (in collateral units × 1e18) |
alpha |
Resolution probability per trade | 1e15 - 1e17 (0.1% - 10%) |
feeBps |
Trading fee | 10-500 (0.1% - 5%) |
minTradesBeforeResolution |
Minimum trades before resolution possible | 1-100 |
maxTradeSize |
Maximum single trade size | Depends on liquidity |
| Operation | Estimated Gas |
|---|---|
| createMarket | ~800,000 |
| buy | ~200,000-300,000 |
| sell | ~200,000-300,000 |
| claim | ~100,000 |
MIT
- Fork the repository
- Create feature branch
- Write tests for new functionality
- Submit pull request
For questions or issues, please open a GitHub issue.
forge verify-contract
--chain base
--watch
--compiler-version v0.8.24
0x7d441a847Ca972F520d728055Cd9E947ac67984A
src/MarketFactoryV2.sol:MarketFactoryV2
--constructor-args $(cast abi-encode "constructor(address,uint256,uint256,bool)" 0x6A9abb446fcA45Dc9BE5Fa3051c4167cd96D1f99 10000000000000000 100 true) \
--etherscan-api-key $ETHERSCAN_API_KEY
forge verify-contract
--chain base
--watch
--compiler-version v0.8.24
0xB138C21Aa20Bf12bCe62DE07cadd9809C607F471
src/mocks/MockERC20.sol:MockUSDC
--etherscan-api-key $ETHERSCAN_API_KEY