Blockchain Sports Betting System Development
Centralized bookmakers—blackbox: they can delay payout, close account, change odds after accepting bet. Blockchain version solves this radically: contract can't not pay if event occurred and oracle confirmed result. But exactly the chain "event → oracle → contract"—the most vulnerable point in system.
Main problem: sports result oracles
Why Chainlink Price Feeds don't help here
Chainlink Data Feeds—aggregated price data from multiple independent nodes. No such infrastructure for sports result: winner data comes from ESPN, Stats Perform, SportRadar. Centralized sources. Chainlink Sports Data (via Any API) or partner adapters—option, but limited league coverage.
Alternatives:
UMA Optimistic Oracle. Works on principle: proposer publishes result + bond, dispute window exists (2-24 hours). If nobody disputed—result accepted. Good for non-urgent events (league final score in 2 days), bad for fast payouts.
Chainlink Functions. Contract makes HTTP request to API source via decentralized node network. Multiple nodes request same API, result aggregated by median. Solves single point of failure, but doesn't solve trusting data source (if ESPN returns wrong result—all nodes swallow it).
Multi-oracle with threshold. Own nodes (3-5) request different data providers. Result accepted if N of M nodes reported same. More expensive infrastructure but full control.
For production system recommend Chainlink Functions + backup UMA Optimistic Oracle with dispute mechanism. On divergence—suspend payouts, manual review.
Manipulation via oracle timing
Attack: attacker knows match result before oracle updates (e.g., via delayed sources). Places bet on winner between match end and oracle update.
Protection: close bet accepting 5-10 minutes before match start (lock period) + don't accept bets when event status = "in progress" per oracle. If oracle doesn't support live status—close bets 30 minutes before kickoff and don't open until final result.
Smart contract architecture
Pari-mutuel vs Fixed odds
Pari-mutuel (tote). Bets on one event form pool, winners divide pool minus fee. Odds not fixed in advance—determined by bet distribution. Simpler to implement on-chain (no market maker risk) and popular in prediction markets like Polymarket.
Fixed odds. Odds fixed when accepting bet. Needs liquidity provider (bookmaker) taking balanced bet risk. More complex, requires either AMM mechanics for dynamic odds or centralized market maker.
For launch—pari-mutuel: fewer protocol risks, simpler audit.
Contract structure
BettingFactory
└── BettingMarket (per event)
├── placeBet(outcome, amount)
├── resolveMarket(result) — only oracle
├── claimWinnings(betId)
└── refund() — if event cancelled
Key BettingMarket parameters:
-
eventId— unique event ID -
lockTimestamp— bet closing moment -
resolutionTimestamp— resolution deadline (if not resolved → refund mode) -
outcomes— allowed outcomes (WIN_HOME, WIN_AWAY, DRAW) -
totalPool[outcome]— amounts per outcome -
oracleAddress— whitelist of oracles
Payout calculation (pari-mutuel)
function calculatePayout(address bettor, uint256 betId) public view returns (uint256) {
Bet memory bet = bets[betId];
require(bet.outcome == winningOutcome, "Not a winner");
uint256 winnerPool = totalPool[winningOutcome];
uint256 totalPoolMinusFee = totalPool[WIN_HOME] + totalPool[WIN_AWAY] + totalPool[DRAW];
totalPoolMinusFee = totalPoolMinusFee * (10000 - protocolFee) / 10000;
return bet.amount * totalPoolMinusFee / winnerPool;
}
Important here: fee must be deducted BEFORE payout calculation, not after. Otherwise protocol collects fee from winners and losers, which is mathematically incorrect.
Refund mechanics on event cancellation
If match didn't happen (rain, force majeure), oracle can't give result. Contract must auto-transition to refund mode on resolutionDeadline expiry. Pull-pattern: each user calls refund(betId) themselves, protocol doesn't distribute funds.
Alternative—off-chain trigger via Chainlink Automation: on deadline without resolution, Automation calls setRefundMode(). UX improvement but optional.
Additional: live betting complexity
Live betting (bets during match)—separate complexity level. Need real-time oracle with minimal lag (< 30 seconds) and mechanism preventing bets after significant events (goal, red card) that happened but not yet updated in oracle.
Technically requires: WebSocket oracle with push updates + freeze period after each update (5-10 seconds without accepting bets). Doable but significantly increases complexity and oracle infrastructure cost.
Development stack
Solidity 0.8.x + OpenZeppelin (AccessControl, Pausable, ReentrancyGuard). Chainlink Functions for oracles. Foundry for testing—fork-tests with mock oracle responses. Hardhat-deploy for reproducible deployment with proxy for upgradability (UUPS).
Frontend: React + wagmi + ethers.js. WalletConnect v2, MetaMask integration. Mobile—Coinbase Wallet SDK.
Process
Oracle model selection (2-3 days). Determine league coverage, latency requirements, oracle infrastructure budget.
Contract development (2-4 weeks). Factory + Market + Oracle integration + tests.
Frontend (1-2 weeks). Bet UI, odds, history, payouts.
Audit. Financial contracts with user funds—audit mandatory.
Deployment. Polygon or Arbitrum (low gas). Gnosis Safe for admin.
Timeline estimates
Pari-mutuel system with basic oracle (one league): 3-5 weeks. Multi-league platform with live betting and custom odds AMM: 2-3 months.







