Розробка системи ставок на спорт на блокчейні
Централізовані букмекери — blackbox: вони можуть затримати виплату, закрити аккаунт, змінити коефіцієнт після прийняття ставки. Блокчейн-версія вирішує це радикально: контракт не може не заплатити, якщо подія наступила та оракул підтвердив результат. Але саме зв'язка «подія → оракул → контракт» — найуразливіше місце у системі.
Головна проблема: оракули спортивних результатів
Чому Chainlink Price Feeds тут не допоможуть
Chainlink Data Feeds — це агреговані price дані від множини незалежних нод. Для спортивного результату немає такої інфраструктури: дані про переможця матчу приходять з ESPN, Stats Perform, SportRadar. Це centralized джерела. Chainlink Sports Data (на базі Any API) або партнерські адаптери — варіант, але з обмеженим покриттям ліг.
Альтернативи:
UMA Optimistic Oracle. Працює за принципом: proposer публікує результат + bond, є dispute window (2-24 години). Якщо ніхто не оспорив — результат прийнятий. Добре для некраєугольних подій (фіналу лігу через 2 дні), погано для швидких виплат.
Chainlink Functions. Контракт робить HTTP-запит до API джерела через децентралізовану мережу нод. Кілька нод запитують один і той ж API, результат агрегується по медіані. Вирішує проблему single point of failure, але не вирішує проблему довіри до джерела даних (якщо ESPN вернеме неверний результат — всі ноди це проглотять).
Multi-oracle з threshold. Власні ноди (3-5 штук) запитують різні data providers. Результат приймається, якщо N з M нод повідомили одинаковий. Дорожче в інфраструктурі, але повний контроль.
Для production системи рекомендуємо Chainlink Functions + резервний UMA Optimistic Oracle з dispute механізмом. При розходженні — suspend виплати, ручний разбір.
Manipulation через oracle timing
Атака: злоумисник знає результат матчу раніше, ніж оракул оновить дані (наприклад, через джерела з затримкою). Робить ставку на переможця у момент між закінченням матчу та оновленням оракула.
Захист: закривати приймання ставок за 5-10 хвилин до початку матчу (lock period) + не приймати ставки коли event status = "in progress" по оракульним даним. Якщо оракул не підтримує live status — закривати ставки за 30 хвилин до kickoff та не відкривати до фінального результату.
Архітектура смарт-контрактів
Pari-mutuel vs Fixed odds
Pari-mutuel (тотализатор). Ставки на одну подію формують пул, переможці ділять пул мінус комісія. Коефіцієнт не фіксується заранее — визначається розподілом ставок. Простіше реалізувати on-chain (немає ризику market maker) та популярно у предикшн маркетах типу Polymarket.
Fixed odds. Коефіцієнт фіксується при прийманні ставки. Потрібен провайдер ліквідності (букмекер), що берет на себе ризик незбалансованих ставок. Складніше, вимагає або AMM-механіку для динамічних odds, або централізованого market maker.
Для старту — pari-mutuel: менше ризиків для протоколу, простіше аудит.
Структура контрактів
BettingFactory
└── BettingMarket (per event)
├── placeBet(outcome, amount)
├── resolveMarket(result) — only oracle
├── claimWinnings(betId)
└── refund() — if event cancelled
Ключові параметри BettingMarket:
-
eventId— унікальний ID події -
lockTimestamp— момент закриття ставок -
resolutionTimestamp— крайній строк резолюції (якщо не resolved → refund режим) -
outcomes— допустимі исходи (WIN_HOME, WIN_AWAY, DRAW) -
totalPool[outcome]— суми по кожному исходу -
oracleAddress— whitelist оракулів
Розрахунок виплати (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;
}
Тут важливо: комісія повинна вичітатися ДО розрахунку виплати, не після. Інакше протокол збирає комісію і з переможців, і з програвців, що математично некоректно.
Refund механіка при скасуванні события
Якщо матч не відбувся (дощ, force majeure), оракул не може дати результат. Контракт повинен перейти у refund mode автоматично по закінченню resolutionDeadline. Pull-паттерн: кожен користувач сам викликає refund(betId), протокол не рассилає кошти.
Альтернатива — off-chain trigger через Chainlink Automation: при наступленні deadline без resolution, Automation викликає setRefundMode(). UX поліпшення, але опціональне.
Додатково: live betting складність
Live betting (ставки під час матчу) — окремий рівень складності. Потрібен real-time оракул з мінімальною затримкою (< 30 секунд) та механізм, що запобігає ставкам після значимих подій (гол, червона карточка), що вже відбулися але ще не оновилися у оракулі.
Технічно вимагає: WebSocket оракул з push-оновленнями + freeze period після кожного оновлення (5-10 секунд без прийому ставок). Реалізуємо, але значно збільшує складність та вартість оракульної інфраструктури.
Стек розробки
Solidity 0.8.x + OpenZeppelin (AccessControl, Pausable, ReentrancyGuard). Chainlink Functions для оракулів. Foundry для тестування — fork-тести з mock oracle responses. Hardhat-deploy для воспроизводимого деплою з proxy для upgradability (UUPS).
Frontend: React + wagmi + ethers.js. Інтеграція з WalletConnect v2, MetaMask. Для мобільного — Coinbase Wallet SDK.
Процес роботи
Вибір оракульної моделі (2-3 дні). Визначаємо покриття ліг, latency вимоги, бюджет на оракульну інфраструктуру.
Розробка контрактів (2-4 тижні). Factory + Market + Oracle integration + тести.
Frontend (1-2 тижні). UI ставок, коефіцієнти, історія, виплати.
Аудит. Фінансові контракти з користувальницькими коштами — аудит обов'язковий.
Деплой. Polygon або Arbitrum (низький газ). Gnosis Safe для admin.
Орієнтири за часом
Pari-mutuel система з базовим оракулом (одна ліга): 3-5 тижнів. Мультілігова платформа з live betting та custom odds AMM: 2-3 місяці.







