Розробка гри Roulette на блокчейні

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка гри Roulette на блокчейні
Середній
~5 днів
Часті запитання

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

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Розробка гри Roulette на блокчейні

Рулетка на блокчейні — класичний use case для provably fair гемблингу. Головна інженерна задача: забезпечити непередбачуваність результату, яку ніхто не може маніпулювати — ні казино ні гравець. Рішення — verifiable randomness через Chainlink VRF або commit-reveal схему.

Randomness: головна проблема

Не можна використовувати block.timestamp, block.prevrandao, або block hashes як джерело randomness. Miner/validator може впливати на ці значення — miner extractable value (MEV) атака на randomness. Для рулетки: нода може вибирати включати або ні транзакцію залежно від winning block.

Chainlink VRF v2.5

Криптографічно безпечна, verifiable random від decentralized oracle network:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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;
    uint256 public currentRoundId;
    uint256 public constant MAX_BET = 1 ether;
    uint256 public constant HOUSE_EDGE = 270; // 2.7%

    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
        });
    }

    // Закрити раунд і запросити randomness
    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++;
    }

    // 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;

        _settleAllBets(roundId);
    }

    function _settleAllBets(uint256 roundId) internal {
        Round storage round = rounds[roundId];

        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, round.result);
            if (payout > 0) {
                payable(bet.player).transfer(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;
        } 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);
    }

    // Червоні числа європейської рулетки
    function _isRed(uint8 n) internal pure returns (bool) {
        uint256 redNumbers = 0x3A4A5251412C2B1A191009080706;
        return (redNumbers >> n) & 1 == 1;
    }
}

Frontend і анімація

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

  • Анімація колеса запускається при виклику spinWheel(), ще до отримання результату від VRF
  • Реальний результат приходить за ~30–60 секунд (час VRF callback)
  • Колесо крутиться кілька повних оборотів, потім плавно тормозить на потрібному секторі
  • Event RoundSettled від контракту — триггер для фінальної позиції

Мережі і стоимість

Основний параметр: стоимість VRF callback. Дорого на Ethereum mainnet. Рекомендовані мережі:

Мережа VRF Cost Час ответа Рекомендація
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 (значно дешевше), або commit-reveal (миттєво але менш децентралізовано).