Overview
Permissionless prediction markets on Polygon. Open source. Immutable. No company. No admin. No servers. Just contracts.
Anyone creates a market. Anyone trades. Anyone builds a frontend. All frontends share the same liquidity. 0.1% fee on every matched trade goes to the treasury - forever.
How it works
-
Create a market - Call
createMarket("Will X happen by Y?", 2, deadline, resolver). Done. A binary YES/NO market exists on-chain. The resolver is chosen at creation time and cannot be changed. -
Trade - Traders place limit orders on the order book. When two opposing orders match, USDC goes into escrow and both sides receive outcome tokens (YES or NO) via the Gnosis Conditional Token Framework.
-
Resolve - After the deadline, anyone can trigger resolution through the market's resolver. For UMA: post a 750 USDC minimum bond, wait 2 hours. If no one disputes, the market resolves. If disputed, UMA's decentralized voter network decides.
-
Redeem - Winners redeem their outcome tokens for USDC through the CTF. Losers get nothing.
Contracts
Core (immutable, deployed once, never changes):
| Contract | What it does |
|---|---|
| MarketFactory | Creates markets. Tracks market state. Reports payouts. forceVoid for stuck markets. |
| OrderBook | On-chain limit order book. BUY/SELL orders, matching, cancellation. Collects fees. |
| Treasury | Holds protocol fees. 0.1% per matched trade. Immutable recipient. Anyone can withdraw to recipient. |
| NegRiskAdapter | Decomposes multi-outcome markets ("Who wins?") into independent binary markets. |
Modules (pluggable, anyone deploys):
| Contract | What it does |
|---|---|
| UMAResolver | UMA Optimistic Oracle integration. Assertions, disputes, settlement callbacks. |
| KalshiResolver | Links markets to Kalshi tickers. Resolves from Kalshi outcomes. |
| PolymarketResolver | Links markets to Polymarket conditions. Resolves from Polymarket payouts. |
Anyone can build and deploy new resolver modules. The factory doesn't care - it just needs a resolver address at market creation time.
Stack
- Chain: Polygon (USDC settlement, sub-cent transactions)
- Tokens: Gnosis CTF (ERC-1155 outcome tokens) -
0x4D97DCd97eC945f40cF65F87097ACe5EA0476045 - Resolution: Pluggable - per-market resolver chosen at creation time (UMA, Kalshi, Polymarket, or anything)
- Collateral: USDC (native on Polygon) -
0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 - Language: Solidity 0.8.24
- Framework: Foundry
Quick start
// 1. Create a market (resolver is chosen at creation time)
bytes32 conditionId = factory.createMarket("Will ETH hit $10k by Dec 2026?", 2, 1767225600, resolverAddress);
// 2. Approve USDC for the order book
usdc.approve(address(orderBook), type(uint256).max);
// 3. Place a BUY order on YES at $0.65 for 100 shares
uint256 orderId = orderBook.placeBuyOrder(conditionId, 0, 650000, 100e6);
// 4. Anyone can match opposing orders (opposingOrderId is a BUY on the opposite outcome)
orderBook.fillBuyVsBuy(orderId, opposingOrderId, 100e6);
// 5. After deadline + assertion + 2hr liveness: redeem winnings
uint256[] memory indexSets = new uint256[](1);
indexSets[0] = 1; // YES = 1 << 0
ctf.redeemPositions(address(usdc), bytes32(0), conditionId, indexSets);Price scale
Prices are on the USDC decimal scale: 1e6 = $1.00 = 100% probability.
| Price | USDC | Probability |
|---|---|---|
| 100000 | $0.10 | 10% |
| 500000 | $0.50 | 50% |
| 900000 | $0.90 | 90% |
A YES token at $0.65 means the market thinks there's a 65% chance of YES.
Key design decisions
No admin, no governance, no upgrades. Core contracts are immutable. No owner. No proxy. No timelock. Deploy and walk away.
Binary markets only on the order book. Multi-outcome markets decompose into binary markets via NegRiskAdapter. "Who wins the election?" with 5 candidates becomes 5 independent "Will X win? YES/NO" markets. This is how Polymarket does it.
Trading stays open until resolution, not deadline. The deadline is when the market can be resolved. Traders can exit positions after the deadline but before resolution.
No on-chain order sorting. Orders are stored flat. Sorting and matching happens off-chain by keepers and frontends. This keeps gas costs constant regardless of book depth.
Permissionless matching. Anyone calls fillBuyVsBuy() or fillBuyVsSell(). No privileged matcher. Off-chain discovery, on-chain execution.
Per-market resolver. Each market chooses its resolver at creation time. UMA, Kalshi, Polymarket, or any custom resolver. The factory doesn't care - it just needs a resolver that calls resolveMarket() when it's time.
Voiding stuck markets. If a market goes 90 days past deadline with no pending resolutions, anyone can void it via factory.forceVoid(). Emergency path at 180 days works even with pending resolutions. Equal payouts either way.