Розробка системи ставок на eventi на блокчейні
Polymarket обробляє сотні мільйонів доларів у обсязі ставок на політичні eventi — та робить це через Polygon з USDC як розрахунковою валютою. Вся система ставок — це смарт-контракти з умовним результатом. Ключове запитання при розробці будь-якої подібної системи не «як прийняти ставку», а «хто та як резолвує результат».
Оракульне питання — центральний інженерний виклик
Результат события «Байден виграє вибори» неможливо отримати з Chainlink Price Feed. Це не числове значення з децентралізованим консенсусом — це судження. Для систем предсказань це означає один з кількох підходів до резолюції.
UMA Optimistic Oracle
UMA використовує optimistic модель: будь-хто може запропонувати результат, інший учасник може оспорити. Оспорювання запускає dispute resolution через UMA token holders голосування. Економічна безпека тримається на bond: пропонуючий результат вносить залог USDC або UMA, який втрачає при неправильній резолюції.
Інтеграція з 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 {
// Вносимо bond, робимо assertion
assertionId = oracle.assertTruth(
claim, // "Team A won the match"
address(this), // asserter
address(0), // callbackRecipient (ми)
address(0), // escalationManager
7200, // challengeWindow: 2 години
IERC20(currency),
bond,
identifier,
bytes32(0)
);
}
function assertionResolvedCallback(bytes32 _assertionId, bool assertedTruthfully) external {
require(msg.sender == address(oracle));
if (assertedTruthfully) {
_settleWinners();
} else {
_refundBettors();
}
}
}
UMA перевага: дешево для більшості ринків (жодного голосування при відсутності dispute), дорого та повільно при оспорюванні.
Chainlink для числових результатів
Коли результат числовий («ціна BTC буде вище $100K на 31 грудня»), Chainlink AggregatorV3Interface — правильний вибір. Детерміністичний, дешевий, без 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);
}
Критична проблема: Chainlink апдейтит дані за deviation threshold або heartbeat — не на конкретну секунду. Якщо ринок закривається в момент між апдейтами, latestRoundData повертає годинної (або старші) дані. Потрібно явно перевіряти updatedAt та мати fallback.
Мультиоракульний підхід для production
Для критично важливих ринків з великими обсягами — агрегація кількох джерел:
| Оракул | Тип результату | Latency | Вартість |
|---|---|---|---|
| Chainlink | Числовий (price) | ~1 година | Низька |
| Pyth Network | Числовий (price) | Секунди | Низька |
| UMA Optimistic | Будь-який | 2+ години | Середня |
| API3 dAPI | Числовий (first-class data) | Хвилини | Низька |
| Custom multisig | Будь-який | Миттєво | 0 (доверо) |
Для спортивних результатів або політики оптимален UMA + резервний multisig (5-з-9 доверених адрес) з timelock. Multisig тільки як остання лінія, якщо UMA dispute затягнувся.
Моделі розподілу виграшу
Fixed odds (букмекерська модель)
Коефіцієнти фіксуються при створенні ринку. Букмекер несе ризик при неправильній оцінці ймовірностей. Складно реалізувати on-chain без централізованої переоцінки odds.
Parimutuel (тоталізатор)
Всі ставки попадають у пул. Переможці діляють пул пропорційно ставкам. Коефіцієнт визначається остаточним розподілом ставок, не відомий заздалегідь.
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 підхід
Polymarket використовує conditional tokens (ERC-1155 стандарт, Gnosis Conditional Tokens Framework). Кожен результат — це ERC-1155 токен. Ринок conditional tokens торгується через CLOB з лімітними ордерами.
Перевага: справжній price discovery, ліквідність агрегується, маркет-мейкери можуть брати участь. Складність: потрібен off-chain orderbook + on-chain settlement, matching engine.
Створення та управління ринком
Factory pattern для ринків
Кожен ринок — окремий контракт, деплойований через фабрику. Це ізолює ризики: проблема одного ринку не впливає на інші.
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 та refund
Якщо оракул не може резолвувати результат (событие скасовано, дані недоступні), потрібен emergency механізм для повернення ставок. Emergency admin (multisig) викликає cancelMarket(), всі учасники можуть забрати депозити. Timelock на скасування — мінімум 24 години, щоб запобігти зловживанням зі сторони admin.
Compliance та front-running захист
Commit-reveal схема для крупних ставок: користувач спочатку відправляє commit(keccak256(amount, secret)), через N блоків — reveal(amount, secret). Це запобігає front-running на популярних результатах перед закриттям ринку.
Для спортивних ставок — часова блокування за 5 хвилин до старту события. Після старту нові ставки не приймаються. Реалізується через endBettingTime = eventStartTime - 5 minutes.
Стек розробки
Контракти: Solidity 0.8.24 + OpenZeppelin (AccessControl, Pausable, ReentrancyGuard) + Foundry для тестування. Тести включають fork-тест з реальним UMA та Chainlink на Polygon mainnet.
Для conditional tokens (якщо обрана Polymarket-like архітектура): Gnosis Conditional Tokens Framework (github.com/gnosis/conditional-tokens-contracts) як базова бібліотека.
Off-chain: The Graph субграф для індексування ринкових подій, ставок, резолюцій. GraphQL API для фронтенду.
Процес роботи
Аналітика (2–3 дні). Тип подій, модель розподілу виграшу, вибір оракула, compliance вимоги, цільові чейни.
Проектування (3–5 днів). Схема контрактів, оракульна інтеграція, механізм резолюції, emergency процедури.
Розробка (3–8 тижнів). Залежно від складності — від parimutuel MVP до повнофункціонального CLOB з conditional tokens.
Аудит. Ставочні контракти — високопріоритетний аудит через прямого управління грошима користувачів та складної логіки резолюції.
Орієнтири за часовими рамками
Простий parimutuel ринок з Chainlink оракулом — 1–2 тижні. Система з UMA oracle, factory pattern, кількома ринками, The Graph індексацією — 6–10 тижнів. CLOB з conditional tokens та маркет-мейкинг інфраструктурою — 3–5 місяців.







