Блокчейн-рулетка на Solidity: VRF, смарт-контракты и React-интерфейс

Архитектура блокчейн-рулетки Обеспечить непредсказуемость результата в on-chain рулетке — нетривиально. Наивные реализации используют block.prevrandao или хэши блоков, но это открывает путь для MEV-атак: валидатор может переупорядочить транзакции, чтобы повлиять на исход. Мы разрабатываем смарт-к

Направления блокчейн-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1003

Архитектура блокчейн-рулетки

Обеспечить непредсказуемость результата в on-chain рулетке — нетривиально. Наивные реализации используют block.prevrandao или хэши блоков, но это открывает путь для MEV-атак: валидатор может переупорядочить транзакции, чтобы повлиять на исход. Мы разрабатываем смарт-контракты с проверяемой случайностью — либо через Chainlink VRF, либо по commit-reveal схеме. Первый дороже, но полностью децентрализован; второй быстрее, но требует доверия к серверу. Ниже — полная архитектура с кодом, выбором сети и анимацией.

За 5+ лет разработки рулеток на блокчейне мы запустили более 20 проектов: от казуальных игр до крупных DeFi gaming платформ. Опыт с разными L1/L2 позволяет выбрать оптимальный стек под бюджет и аудиторию.

Как обеспечить честную случайность?

Нельзя использовать block.timestamp, block.prevrandao, или хэши блоков как источник случайности. Miner/validator может влиять на эти значения — это называется miner extractable value (MEV) атака на randomness. Для рулетки это означает, что нода может выбирать, включать или нет транзакцию в зависимости от того, выигрышный ли блок.

Chainlink VRF v2.5

Cryptographically secure, verifiable random numbers от децентрализованного оракула. Chainlink VRF гарантирует, что ни казино, ни игрок не могут повлиять на результат, а доказательство верифицируется в контракте.

// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import { VRFConsumerBaseV2Plus } from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol"; import { VRFV2PlusClient } from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol"; contract RouletteGame is VRFConsumerBaseV2Plus { uint256 public subscriptionId; bytes32 public keyHash; // зависит от сети enum BetType { Number, Red, Black, Even, Odd, Low, High } struct Bet { address player; BetType betType; uint8 number; // для Number ставки (0-36) uint256 amount; bool settled; } struct Round { uint256 requestId; uint8 result; // 0-36 bool fulfilled; mapping(uint256 => Bet) bets; uint256 betCount; } mapping(uint256 => Round) public rounds; // requestId → Round mapping(uint256 => uint256) public requestToRound; uint256 public currentRoundId; uint256 public constant MAX_BET = 1 ether; uint256 public constant HOUSE_EDGE = 270; // 2.7% (европейская рулетка) event BetPlaced(uint256 roundId, address player, BetType betType, uint8 number, uint256 amount); event RoundStarted(uint256 roundId, uint256 requestId); event RoundSettled(uint256 roundId, uint8 result); event WinningPaid(address player, uint256 amount); constructor( address vrfCoordinator, uint256 _subscriptionId, bytes32 _keyHash ) VRFConsumerBaseV2Plus(vrfCoordinator) { subscriptionId = _subscriptionId; keyHash = _keyHash; currentRoundId = 1; } function placeBet( BetType betType, uint8 number ) external payable { require(msg.value > 0 && msg.value <= MAX_BET, "Invalid bet amount"); if (betType == BetType.Number) { require(number <= 36, "Invalid number"); } Round storage round = rounds[currentRoundId]; uint256 betId = round.betCount++; round.bets[betId] = Bet({ player: msg.sender, betType: betType, number: number, amount: msg.value, settled: false }); emit BetPlaced(currentRoundId, msg.sender, betType, number, msg.value); } // Закрыть раунд и запросить случайность function spinWheel() external returns (uint256 requestId) { requestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: keyHash, subId: subscriptionId, requestConfirmations: 3, callbackGasLimit: 300_000, numWords: 1, extraArgs: VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({ nativePayment: false }) ) }) ); requestToRound[requestId] = currentRoundId; rounds[currentRoundId].requestId = requestId; currentRoundId++; emit RoundStarted(currentRoundId - 1, requestId); } // Callback от Chainlink VRF function fulfillRandomWords( uint256 requestId, uint256[] calldata randomWords ) internal override { uint256 roundId = requestToRound[requestId]; Round storage round = rounds[roundId]; // 0-36 включительно = 37 значений uint8 result = uint8(randomWords[0] % 37); round.result = result; round.fulfilled = true; emit RoundSettled(roundId, result); _settleAllBets(roundId); } function _settleAllBets(uint256 roundId) internal { Round storage round = rounds[roundId]; uint8 result = round.result; for (uint256 i = 0; i < round.betCount; i++) { Bet storage bet = round.bets[i]; if (bet.settled) continue; bet.settled = true; uint256 payout = _calculatePayout(bet, result); if (payout > 0) { payable(bet.player).transfer(payout); emit WinningPaid(bet.player, payout); } } } function _calculatePayout( Bet memory bet, uint8 result ) internal pure returns (uint256) { bool win = false; uint256 multiplier = 0; if (bet.betType == BetType.Number) { win = (bet.number == result); multiplier = 35; // 35:1 } else if (bet.betType == BetType.Red) { win = _isRed(result); multiplier = 1; // 1:1 } else if (bet.betType == BetType.Black) { win = (!_isRed(result) && result != 0); multiplier = 1; } else if (bet.betType == BetType.Even) { win = (result != 0 && result % 2 == 0); multiplier = 1; } else if (bet.betType == BetType.Odd) { win = (result % 2 == 1); multiplier = 1; } else if (bet.betType == BetType.Low) { win = (result >= 1 && result <= 18); multiplier = 1; } else if (bet.betType == BetType.High) { win = (result >= 19 && result <= 36); multiplier = 1; } if (!win) return 0; return bet.amount + (bet.amount * multiplier); // stake + profit } // Красные числа европейской рулетки function _isRed(uint8 n) internal pure returns (bool) { uint256 redNumbers = 0x3A4A5251412C2B1A191009080706; return (redNumbers >> n) & 1 == 1; } // Пополнение пула призов receive() external payable {} // Emergency: вывод призового пула (timelock + multisig в production) function withdrawHouseBalance(uint256 amount) external onlyOwner { payable(owner()).transfer(amount); } } 

Почему стоит выбрать Chainlink VRF?

Chainlink VRF предоставляет доказательство случайности, которое можно проверить в контракте. Это значит, что игрок может самостоятельно убедиться в честности раунда. Альтернативные решения, такие как commit-reveal с серверным seed, не предоставляют ончейн-верификации, но значительно дешевле. Для low-stake игр или тестнетов их можно рассматривать.

Комиссия и ликвидность

Казино должно иметь достаточный резерв для выплаты крупных выигрышей. Распространённые подходы:

Fixed house reserve — контракт держит фиксированный резерв, максимальная ставка ограничена процентом от резерва.

Liquidity pool модель — LP провайдеры депонируют ETH, получают долю house edge как доходность. Игроки играют против пула. Смарт-контракт вроде Liquidity.lp token. Эта модель популярна в крупных блокчейн-гемблинг проектах (Rollbit, Stake Casino).

Фронтенд и анимация

Сама рулетка — DOM canvas (React + Pixi.js или Three.js). Ключевые моменты:

  • Анимация колеса запускается при вызове spinWheel(), ещё до получения результата от VRF
  • Реальный результат приходит через ~30–60 секунд (время VRF callback)
  • Колесо "случайно вращается" несколько полных оборотов, затем плавно тормозит на нужном секторе
  • Event RoundSettled от контракта — триггер для финальной позиции колеса
// Подписка на событие результата const unwatch = publicClient.watchContractEvent({ address: ROULETTE_ADDRESS, abi: rouletteAbi, eventName: "RoundSettled", args: { roundId: currentRoundId }, onLogs: (logs) => { const result = logs[0].args.result; rouletteWheel.stopAt(result); // анимация финального положения showResult(result); checkWinnings(result); }, }); 

Сети и стоимость

Основной параметр выбора сети — стоимость VRF callback. На Ethereum mainnet это дорого. Использование L2 сетей снижает затраты на VRF в 10-20 раз. Рекомендуемые сети:

Сеть VRF стоимость Время ответа Рекомендация
Arbitrum ~$0.30–0.80 ~30–60 сек Оптимально
Polygon ~$0.01–0.05 ~30–60 сек Бюджетный вариант
Avalanche ~$0.10–0.30 ~30–60 сек Хорошо
Base ~$0.05–0.20 ~30–60 сек Растущая аудитория

Для high-frequency вращений (несколько в минуту) — рассматриваем alternative VRF: Pyth Entropy (значительно дешевле Chainlink), или commit-reveal с server seed (менее decentralized, но мгновенно).

Процесс разработки под ключ

Мы работаем по чёткому плану:

  1. Аналитика и проектирование — фиксируем правила игры, виды ставок, house edge, механику выплат.
  2. Разработка смарт-контракта — пишем контракт на Solidity 0.8.x с поддержкой VRF, multiple bet types, payout math.
  3. Тестирование и аудит — unit тесты (Foundry), fuzzing (Echidna), статический анализ (Slither, Mythril). Сертифицированные Solidity-разработчики проводят ревью кода.
  4. Фронтенд интеграция — React + wagmi + RainbowKit, анимация колеса на Pixi.js/Three.js, подписка на события контракта.
  5. Деплой и запуск — развертывание в выбранной сети, настройка Chainlink VRF subscription, финальный smoke test.

Что входит в работу

Компонент Описание
Смарт-контракт Solidity, ERC-20/ETH, VRF, все ставки
Анимация колеса React + Pixi.js, синхронизация с событием контракта
Бэкенд (опционально) Event sourcing, лидерборд, админка
Документация API контракта, инструкция по запуску
Поддержка 2 недели после запуска, обучение команды

Сроки и стоимость

Сроки разработки — от 4 до 8 недель в зависимости от сложности. Точная стоимость рассчитывается индивидуально после анализа ваших требований. Готовы обсудить проект? Свяжитесь с нами для предварительной оценки. Закажите разработку рулетки под ключ — получите готовый продукт с гарантией качества.