Розробка гри Wheel of Fortune на блокчейні

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка гри Wheel of Fortune на блокчейні
Середній
~3-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

Розробка гри Wheel of Fortune на блокчейні

Колесо Фортуни — один з найпростіших механізмів азартних ігор: гравець ставить, рулетка крутиться, випадає сектор, виплата за коефіцієнтом. Вся суть зводиться до одного питання: хто генерує випадкове число й чи можна цьому довіряти? Без чесної рандомізації blockchain wheel of fortune — просто гарний інтерфейс навколо шахрайства.

Це робить Chainlink VRF (Verifiable Random Function) центральним технічним елементом. Не факультативно — обов'язково. Будь-яке інше рішення або передбачуване, або залежить від маніпуляції оператора.

Чому стандартні джерела рандома не працюють

block.timestamp, block.prevrandao — контролюються валідаторами. Майнер може вибрати не включати транзакцію, якщо бачить несприятливий результат (grinding attack). Для високих ставок це прямий вектор експлуатації.

On-chain хеш майбутнього блоку — аналогічна проблема. Нечесний оператор бачить хеш, може скасувати reveal якщо результат несприятливий.

Off-chain оракул без proof — повна довіра до оператора. Користувач не може перевірити, що число не було обрано постфактум.

Chainlink VRF v2.5 — єдине готове до production рішення: випадкове число генерується з криптографічним proof, верифікованим on-chain. Оператор фізично не може маніпулювати результатом.

Інтеграція Chainlink VRF

Підхід на основі підписки

VRF v2.5 працює через підписку: створюєш підписку, поповнюєш LINK токенами, контракт робить запити через subscription ID.

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 WheelOfFortune is VRFConsumerBaseV2Plus {
    // Параметри Chainlink VRF (Ethereum mainnet)
    bytes32 constant KEY_HASH = 0x9fe0eebf5e446e3c998ec9bb19951541aee00bb90ea201ae456421a2ded86805;
    uint256 immutable subscriptionId;
    uint32 constant CALLBACK_GAS_LIMIT = 100_000;
    uint16 constant REQUEST_CONFIRMATIONS = 3;

    struct Spin {
        address player;
        uint256 betAmount;
        uint8 wheelType;       // 0=standard, 1=premium (різні набори секторів)
        uint256 requestId;
        bool fulfilled;
    }

    mapping(uint256 => Spin) public spins;       // requestId => Spin
    mapping(address => uint256) public pendingSpins; // player => requestId

    event SpinRequested(address indexed player, uint256 indexed requestId, uint256 betAmount);
    event SpinResult(address indexed player, uint256 indexed requestId, uint8 sector, uint256 payout);

    function spin(uint8 wheelType) external payable {
        require(msg.value >= MIN_BET && msg.value <= MAX_BET, "Invalid bet");
        require(pendingSpins[msg.sender] == 0, "Spin pending");

        uint256 requestId = s_vrfCoordinator.requestRandomWords(
            VRFV2PlusClient.RandomWordsRequest({
                keyHash: KEY_HASH,
                subId: subscriptionId,
                requestConfirmations: REQUEST_CONFIRMATIONS,
                callbackGasLimit: CALLBACK_GAS_LIMIT,
                numWords: 1,
                extraArgs: VRFV2PlusClient._argsToBytes(
                    VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
                )
            })
        );

        spins[requestId] = Spin({
            player: msg.sender,
            betAmount: msg.value,
            wheelType: wheelType,
            requestId: requestId,
            fulfilled: false
        });
        pendingSpins[msg.sender] = requestId;

        emit SpinRequested(msg.sender, requestId, msg.value);
    }

    function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords)
        internal override {
        Spin storage s = spins[requestId];
        require(!s.fulfilled, "Already fulfilled");
        s.fulfilled = true;
        delete pendingSpins[s.player];

        // Визначаємо сектор колеса
        uint8 sector = _getSector(randomWords[0], s.wheelType);
        uint256 payout = _calculatePayout(s.betAmount, sector);

        // Виплата
        if (payout > 0) {
            payable(s.player).transfer(payout);
        }

        emit SpinResult(s.player, requestId, sector, payout);
    }
}

Дизайн секторів колеса

Сектори визначають house edge й excitement. Важливо: підсумковий RTP (Return to Player) повинен бути явно заявлений і верифіковний on-chain.

struct Sector {
    string name;
    uint16 weight;       // з 10000 (basis points)
    uint16 multiplier;   // множник x100 (200 = 2x, 500 = 5x, 0 = lose)
}

// Стандартне колесо — сума weight = 10000
Sector[] standardWheel = [
    Sector("2x",   4000, 200),    // 40% шанс, 2x
    Sector("3x",   2000, 300),    // 20% шанс, 3x
    Sector("5x",   1500, 500),    // 15% шанс, 5x
    Sector("10x",  800,  1000),   // 8% шанс, 10x
    Sector("20x",  300,  2000),   // 3% шанс, 20x
    Sector("50x",  100,  5000),   // 1% шанс, 50x
    Sector("MISS", 1300, 0),      // 13% шанс, програш
];

// RTP = sum(weight * multiplier / 100) / 10000
// = (4000*2 + 2000*3 + 1500*5 + 800*10 + 300*20 + 100*50 + 1300*0) / 10000
// = (8000 + 6000 + 7500 + 8000 + 6000 + 5000 + 0) / 10000 = 40500/10000 = 4.05...
// Правильно: RTP = sum(weight/10000 * multiplier/100)
// = 0.4*2 + 0.2*3 + 0.15*5 + 0.08*10 + 0.03*20 + 0.01*50 = 0.8+0.6+0.75+0.8+0.6+0.5 = 4.05
// House edge = 1 - RTP = ... потрібна нормалізація множника до ставки
// RTP = 0.4*2 + 0.2*3 + 0.15*5 + 0.08*10 + 0.03*20 + 0.01*50 + 0.13*0 = 4.05?
// Тут множник = outgoing / bet. RTP як доля ставки = ті ж цифри.
// Для house edge < 1.0: підбираємо ваги під бажаний RTP (зазвичай 90-97%)

function _getSector(uint256 randomWord, uint8 wheelType) internal view returns (uint8) {
    Sector[] storage wheel = wheelType == 0 ? standardWheel : premiumWheel;
    uint256 position = randomWord % 10000;
    uint256 cumulative = 0;

    for (uint8 i = 0; i < wheel.length; i++) {
        cumulative += wheel[i].weight;
        if (position < cumulative) return i;
    }
    return uint8(wheel.length - 1);
}

House Bankroll й ліквідність

Контракт повинен мати баланс для виплати максимального можливого виграшу. Мінімальний bankroll = MAX_BET × max_multiplier. При 50x множнику й MAX_BET 1 ETH — мінімум 50 ETH резерву.

Модель Liquidity provider. Користувачі вносять ETH у пул як LP, отримують частку house edge прибутку. Вирішує проблему bankroll й створює yield-bearing продукт:

mapping(address => uint256) public lpShares;
uint256 public totalShares;
uint256 public houseBalance;

function addLiquidity() external payable {
    uint256 shares = totalShares == 0
        ? msg.value
        : (msg.value * totalShares) / houseBalance;

    lpShares[msg.sender] += shares;
    totalShares += shares;
    houseBalance += msg.value;
}

function removeLiquidity(uint256 shares) external {
    require(lpShares[msg.sender] >= shares, "Insufficient shares");
    uint256 amount = (shares * houseBalance) / totalShares;

    // Перевіряємо достатньо ліквідності після виводу
    require(houseBalance - amount >= MIN_BANKROLL, "Insufficient bankroll");

    lpShares[msg.sender] -= shares;
    totalShares -= shares;
    houseBalance -= amount;
    payable(msg.sender).transfer(amount);
}

Фронтенд: анімація й UX

Візуальна анімація колеса повинна бути детерміністичною від результату VRF — не випадковою на фронтенді. Важливо для сприйняття справедливості: результат уже визначений on-chain, анімація тільки візуалізує його.

// Після отримання SpinResult події
function animateWheel(sector: number, totalSectors: number, onComplete: () => void) {
    const sectorAngle = 360 / totalSectors
    const targetAngle = 360 * 5 + sector * sectorAngle // 5 повних обертів + цільовий сектор

    wheelElement.style.transition = 'transform 4s cubic-bezier(0.17, 0.67, 0.12, 0.99)'
    wheelElement.style.transform = `rotate(${targetAngle}deg)`

    setTimeout(onComplete, 4000)
}

Очікування VRF відповіді. VRF займає 3-5 блоків (~36-60 секунд на Ethereum). Користувач бачить крутяння анімації + таймер. На L2 (Arbitrum, Base) — швидше, 1-3 блоки. Polygon — ще швидше.

Для миттєвого відчуття: показуємо анімацію «крутимо» відразу, чекаємо VRF відповіді, програємо фінальний spin з розкриттям результату.

NFT бусти й ігрові механіки

Spin boost NFT. NFT дають додатковий множник на виграш (+10%), додатковий spin раз на 24 години, доступ до premium колеса з вищими множниками. Створює вторинний ринок й token sink.

Jackpot механіка. Невелика частка кожної ставки (1-2%) йде в jackpot пул. Випадення спеціального сектора «JACKPOT» (дуже малий вік, 0.1%) забирає весь пул. Психологічно привабливий механізм.

Daily bonus spin. Безплатний spin раз на 24 години з обмеженим max виграшем. Підвищує retention без значного впливу на house баланс.

Стек й інфраструктура

Компонент Технологія
Смарт контракти Solidity + Foundry + OpenZeppelin
VRF Chainlink VRF v2.5
Фронтенд React + wagmi + viem
Анімація Framer Motion / GSAP
Events моніторинг viem watchContractEvent
NFT ERC-721 (бусти) + ERC-1155 (cosmetics)
Deploy Arbitrum / Base (низька комісія газу, швидкі блоки)

Процес розробки

Game design (3-5 днів). Дизайн секторів, розрахунок RTP й house edge, LP модель, NFT механіки. Верифікація математики перед кодуванням.

Смарт контракти (2-3 тижні). VRF інтеграція, wheel логіка, LP механізм, NFT контракти. Foundry тести з мокнутим VRF coordinator.

Фронтенд (2-3 тижні). Візуалізація колеса, анімації, очікування VRF, wallet інтеграція, LP dashboard.

Аудит. VRF інтеграція й LP механізм — обов'язковий аудит. Особливу увагу: чи може оператор змінювати сектори без timelock, коректність перевірок bankroll.

Базова версія без LP й NFT — 4-5 тижнів. Повна з LP пулом, jackpot, NFT системою — 8-10 тижнів.