Blockchain roulette game development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Blockchain roulette game development
Medium
~5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Blockchain Roulette Game Development

Roulette on blockchain—classic provably fair gambling use case. Main engineering task: ensure outcome unpredictability no one can manipulate—neither casino nor player. Solution—verifiable randomness via Chainlink VRF or commit-reveal scheme.

Randomness: Main Problem

Can't use block.timestamp, block.prevrandao, or block hashes as randomness source. Miner/validator can influence these—miner extractable value (MEV) attack on randomness. For roulette: node can choose to include or exclude transaction depending on winning block.

Chainlink VRF v2.5

Cryptographically secure, verifiable random from 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;   // for Number bet (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;
    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 RoundSettled(uint256 roundId, uint8 result);
    event WinningPaid(address player, uint256 amount);

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

    // Close round and request 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++;

        emit RoundStarted(currentRoundId - 1, requestId);
    }

    // Callback from Chainlink VRF
    function fulfillRandomWords(
        uint256 requestId,
        uint256[] calldata randomWords
    ) internal override {
        uint256 roundId = requestToRound[requestId];
        Round storage round = rounds[roundId];

        // 0-36 inclusive = 37 values
        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;
        } 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);
    }

    // Red numbers on European roulette
    function _isRed(uint8 n) internal pure returns (bool) {
        uint256 redNumbers = 0x3A4A5251412C2B1A191009080706;
        return (redNumbers >> n) & 1 == 1;
    }

    receive() external payable {}

    function withdrawHouseBalance(uint256 amount) external onlyOwner {
        payable(owner()).transfer(amount);
    }
}

Frontend and Animation

Roulette wheel—DOM canvas (React + Pixi.js or Three.js). Key points:

  • Wheel animation starts on spinWheel() call, before VRF result arrives
  • Real result comes in ~30–60 seconds (VRF callback time)
  • Wheel spins several full rotations, then smoothly stops on correct sector
  • RoundSettled event triggers final position animation
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);
  },
});

Network and Cost

Main parameter: VRF callback cost. Expensive on Ethereum mainnet. Recommended networks:

Network VRF Cost Response Time Recommendation
Arbitrum ~$0.30–0.80 ~30–60 sec Optimal
Polygon ~$0.01–0.05 ~30–60 sec Budget
Avalanche ~$0.10–0.30 ~30–60 sec Good
Base ~$0.05–0.20 ~30–60 sec Growing

For high-frequency spins (several per minute)—consider alternative VRF: Pyth Entropy (significantly cheaper), or commit-reveal (instant but less decentralized).