Blockchain Event Betting System Development
Polymarket processes hundreds of millions in betting volume on political events — doing it via Polygon with USDC as settlement currency. The entire betting system is smart contracts with conditional outcomes. The key question when developing any such system isn't "how to accept a bet," but "who and how resolves the outcome."
The Oracle Question — Central Engineering Challenge
The outcome of "Biden wins the election" can't be obtained from Chainlink Price Feed. It's not a numerical value with decentralized consensus — it's a judgment. For prediction systems, this means one of several resolution approaches.
UMA Optimistic Oracle
UMA uses optimistic model: anyone can propose outcome, another participant can dispute. Disputing triggers resolution via UMA token holders voting. Economic security is based on bond: outcome proposer posts USDC or UMA bond, which they lose on incorrect resolution.
Integration with UMA:
import "@uma/core/contracts/optimistic-oracle-v3/interfaces/OptimisticOracleV3Interface.sol";
contract EventBettingMarket {
OptimisticOracleV3Interface immutable oracle;
bytes32 public assertionId;
function resolveMarket(bytes memory claim) external {
// Post bond, make assertion
assertionId = oracle.assertTruth(
claim, // "Team A won the match"
address(this), // asserter
address(0), // callbackRecipient (us)
address(0), // escalationManager
7200, // challengeWindow: 2 hours
IERC20(currency),
bond,
identifier,
bytes32(0)
);
}
function assertionResolvedCallback(bytes32 _assertionId, bool assertedTruthfully) external {
require(msg.sender == address(oracle));
if (assertedTruthfully) {
_settleWinners();
} else {
_refundBettors();
}
}
}
UMA advantage: cheap for most markets (no voting if no dispute), expensive and slow if disputed.
Chainlink for Numeric Outcomes
When outcome is numeric ("BTC price will exceed $100K on Dec 31"), Chainlink AggregatorV3Interface is the right choice. Deterministic, cheap, no dispute window.
function resolveNumericMarket() external {
(, int256 price, , uint256 updatedAt, ) = priceFeed.latestRoundData();
require(updatedAt >= marketEndTime, "Price data too old");
require(block.timestamp >= marketEndTime, "Market not ended");
bool outcomeA = price >= int256(targetPrice);
_settleMarket(outcomeA);
}
Critical problem: Chainlink updates by deviation threshold or heartbeat — not at specific second. If market closes between updates, latestRoundData returns hour-old (or older) data. Must explicitly check updatedAt and have fallback.
Multi-Oracle Approach for Production
For critical markets with large volumes — aggregate multiple sources:
| Oracle | Outcome Type | Latency | Cost |
|---|---|---|---|
| Chainlink | Numeric (price) | ~1 hour | Low |
| Pyth Network | Numeric (price) | Seconds | Low |
| UMA Optimistic | Any | 2+ hours | Medium |
| API3 dAPI | Numeric (first-class data) | Minutes | Low |
| Custom multisig | Any | Instant | 0 (trust) |
For sports or politics, optimal is UMA + backup multisig (5-of-9 trusted addresses) with timelock. Multisig only as last resort if UMA dispute stalls.
Payout Distribution Models
Fixed Odds (Sportsbook Model)
Odds fixed at market creation. Bookmaker bears risk if odds mispriced. Hard to implement on-chain without centralized odds repricing.
Parimutuel (Tote)
All bets go into pool. Winners split pool proportionally to their bets. Odds determined by final bet distribution, unknown beforehand.
mapping(uint8 => uint256) public totalBets; // outcome => total ETH bet
mapping(address => mapping(uint8 => uint256)) public userBets;
function claimWinnings(uint8 outcome) external {
require(resolvedOutcome == outcome, "Wrong outcome");
uint256 userBet = userBets[msg.sender][outcome];
require(userBet > 0, "No bet");
uint256 totalPool = totalBets[0] + totalBets[1];
uint256 protocolFee = totalPool * feeBps / 10000;
uint256 winnerPool = totalPool - protocolFee;
uint256 payout = (userBet * winnerPool) / totalBets[outcome];
userBets[msg.sender][outcome] = 0;
IERC20(currency).transfer(msg.sender, payout);
}
CLOB (Central Limit Order Book) — Polymarket Approach
Polymarket uses conditional tokens (ERC-1155 standard, Gnosis Conditional Tokens Framework). Each outcome is an ERC-1155 token. Conditional token market trades via CLOB with limit orders.
Advantage: true price discovery, liquidity aggregation, market makers can participate. Complexity: needs off-chain orderbook + on-chain settlement, matching engine.
Market Creation and Management
Factory Pattern for Markets
Each market is separate contract, deployed via factory. Isolates risks: one market's problem doesn't affect others.
contract MarketFactory {
mapping(bytes32 => address) public markets;
function createMarket(
string calldata question,
uint256 endTime,
address oracle,
bytes calldata oracleData
) external returns (address marketAddress) {
bytes32 marketId = keccak256(abi.encode(question, endTime, oracle));
require(markets[marketId] == address(0), "Market exists");
BettingMarket market = new BettingMarket(
question, endTime, oracle, oracleData, feeBps
);
markets[marketId] = address(market);
emit MarketCreated(marketId, address(market), question, endTime);
return address(market);
}
}
Emergency Pause and Refund
If oracle can't resolve outcome (event cancelled, data unavailable), emergency mechanism needed to return bets. Emergency admin (multisig) calls cancelMarket(), all participants withdraw deposits. Timelock on cancellation — minimum 24 hours, prevent admin abuse.
Compliance and Front-Running Protection
Commit-reveal scheme for large bets: user first sends commit(keccak256(amount, secret)), after N blocks — reveal(amount, secret). Prevents front-running on popular outcomes before market close.
For sports betting — betting lock 5 minutes before event start. After start, new bets rejected. Implemented via endBettingTime = eventStartTime - 5 minutes.
Development Stack
Contracts: Solidity 0.8.24 + OpenZeppelin (AccessControl, Pausable, ReentrancyGuard) + Foundry for testing. Tests include fork-test with real UMA and Chainlink on Polygon mainnet.
For conditional tokens (if Polymarket-like architecture): Gnosis Conditional Tokens Framework (github.com/gnosis/conditional-tokens-contracts) as base library.
Off-chain: The Graph subgraph for indexing market events, bets, resolutions. GraphQL API for frontend.
Work Process
Analytics (2–3 days). Event types, payout distribution model, oracle choice, compliance requirements, target chains.
Design (3–5 days). Contract schema, oracle integration, resolution mechanics, emergency procedures.
Development (3–8 weeks). Depending on complexity — from parimutuel MVP to full CLOB with conditional tokens.
Audit. Betting contracts are high-priority audit due to direct user fund management and complex resolution logic.
Timeline Estimates
Simple parimutuel market with Chainlink oracle — 1–2 weeks. System with UMA oracle, factory pattern, multiple markets, The Graph indexing — 6–10 weeks. CLOB with conditional tokens and market-making infrastructure — 3–5 months.







