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

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

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

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

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

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

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

Забезпечити непередбачуваність результату в 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. Ця модель популярна у великих блокчейн-гемблінг проектах.

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

Сама рулетка — 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 тижнів залежно від складності. Точна вартість розраховується індивідуально після аналізу ваших вимог. Готові обговорити проект? Зв'яжіться з нами для попередньої оцінки. Замовте розробку рулетки під ключ — отримайте готовий продукт з гарантією якості.

GameFi розробка: ігрова економіка, контракти та on-chain механіка

Ми бачили цей сценарій не раз. Axie Infinity на піку мала великий дохід, але через 18 місяців токен різко знецінився, а аудиторія значно скоротилася. Причина — відсутність sink'ів: гравці заробляли SLP і виводили, а механізмів спалювання не вистачало. Ми пропонуємо GameFi розробку під ключ: від токеноміки до смарт-контрактів, щоб ваша економіка не повторила цю помилку. Оцінимо ваш проєкт на meetup або онлайн. Наш досвід — 5+ років, десятки реалізованих проєктів, включно з аудитом 15+ P2E ігор.

Чому ламається Play-to-Earn економіка?

Інфляційна токеноміка без sink'ів — головна причина смертельної спіралі. Гравець отримує токени за геймплей. Якщо sink'ів (механізмів спалювання або споживання) недостатньо — supply зростає швидше за demand. Ціна падає. Дохід гравця у fiat зменшується. Гравці йдуть.

Правильна конструкція — dual-token модель з чітким розподілом: governance/value token з обмеженим supply і utility/reward token для внутрішньоігрової економіки. Utility token має активно споживатися: крафтинг предметів, апгрейди, entry fees, breeding. Приклади: GODS/FLUX у Gods Unchained, AXS/SLP у Axie (хоча sink'ів там виявилося недостатньо). Ефективна економіка балансує mint та burn: типовий ratio — 1.2—1.5 burn на 1 mint для запобігання інфляції.

Які sink-механізми реально працюють?

Механізм Опис Приклад
Breeding / крафтинг Спалювання utility token за створення нового NFT Axie (але з правильним balancing)
Апгрейди персонажів Кожна еволюція вимагає спалювання токена Більшість P2E RPG
PvP entry fee Вхід у турнір спалює токени, частина йде в призовий пул Splinterlands
Дурабилити предметів Після N боїв предмет ламається, токен витрачається на ремонт Успадковано з MMORPG
Фінансові механіки Стейкінг з lock-up, що виводить токени з обігу на строк DeFi-шари

Наш досвід — десятки реалізованих проєктів у Web3, включно з аудитом 15+ P2E ігор. Ми знаємо, які sink'и працюють у довгостроковій перспективі. Наприклад, у проєкті з breeding RPG ми збільшили спалювання utility token на 300% за рахунок введення «ремеслу зношуваності» та обов'язкових апгрейдів.

On-chain vs Off-chain: де проходить межа

Всю ігрову логіку on-chain виносити не потрібно — кожна транзакція коштує газу і триває 12 секунд. Ігровий цикл — мілісекунди. Баланс:

Компонент On-chain Off-chain Приклади
Ownership активів + NFT предмети, land
Передача/торгівля + Маркетплейси
Фінанси (стейкінг, rewards) + Staking vaults, DAO
Random generation + (через VRF) Chainlink VRF
Ігровий процес + Бойова система, рух
State ігрового світу + Координати, health points
Матчмейкінг + Серверна логіка

Результати геймплею переносяться на блокчейн через signed message від сервера або ZK-proof. Verifiable off-chain з ZK: ігровий сервер генерує ZK-proof коректності сесії, контракт верифікує proof і нараховує винагороду. Реалізації: Cartridge (Starknet), zkSync game rollups.

Реалізація NFT ігрових предметів

Стандарт: ERC-1155 для взаємозамінних предметів (ресурси, consumables) + ERC-721 для унікальних (персонажі, land). ERC-1155 дає до 60% економії на газі при batch transfer у порівнянні з окремими ERC-721 переказами — для гри з сотнями предметів це економія сотень доларів на день.

Як реалізувати динамічні NFT без перевантаження блокчейна?

Характеристики предмета змінюються в процесі гри (experience, durability, upgrades). Два підходи:

  • Fully on-chain: атрибути зберігаються в mapping контракту, tokenURI генерується з атрибутів через SVG/JSON encoding. Дорого за газом при частому оновленні. Використовується для land і ключових активів.
  • Hybrid: атрибути зберігаються off-chain, в tokenURI — hash стану. Оновлення підписується сервером, верифікується on-chain при transfer або продажі. Дешевше, але вимагає довіри до сервера або ZK.

Breeding і crafting. Контракт: два батьківських NFT → оплата utility token (burn) → мінт нового NFT з атрибутами, що залежать від батьків + Chainlink VRF для випадковості. Без VRF майнери можуть маніпулювати рандомом через вибір блоку.

// Simplified breeding with Chainlink VRF
function breed(uint256 parent1Id, uint256 parent2Id) external {
    require(ownerOf(parent1Id) == msg.sender);
    require(ownerOf(parent2Id) == msg.sender);
    require(breedingToken.burnFrom(msg.sender, BREEDING_COST));

    uint256 requestId = vrfCoordinator.requestRandomWords(...);
    pendingBreeds[requestId] = BreedRequest(parent1Id, parent2Id, msg.sender);
}

function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
    BreedRequest memory req = pendingBreeds[requestId];
    uint256 childAttributes = deriveAttributes(req.parent1Id, req.parent2Id, randomWords[0]);
    _mintWithAttributes(req.requester, childAttributes);
}

Маркетплейс і роялті

Вбудований маркетплейс дає контроль над fee структурою та кастомною логікою (заборона торгівлі предметами до певного рівня). Роялті за EIP-2981 — стандарт, але не enforceable: Blur та інші маркетплейси ігнорують on-chain роялті. Для enforcement — whitelist-only transfer (тільки через контракти, що платять роялті). Жертвуємо composability заради захисту прав.

Staking та розподіл винагород

Staking NFT — механіка для утримання гравців. Проблема: нарахування rewards при тисячах стейкерів вимагає постійних транзакцій (дорого). Рішення — reward-per-share паттерн (як у MasterChef від SushiSwap): глобальний accRewardPerShare, при claim або change state перераховується заборгованість за формулою pendingReward = stakedAmount * (accRewardPerShare - userRewardDebt). O(1) складність незалежно від кількості стейкерів. Економія газу — до 70% порівняно з поелементним нарахуванням.

Детальніше про reward-per-shape реалізацію

Алгоритм працює так: при кожному депозиті/знятті стейку або при оновленні глобального пулу (наприклад, додаванні нових токенів винагороди) контракт оновлює accRewardPerShare = totalPendingReward / totalStaked. Потім для конкретного користувача розраховується pending = user.staked * (accRewardPerShare - user.rewardDebt). Після виплати user.rewardDebt встановлюється рівним accRewardPerShare. Це дозволяє не зберігати історію внесків кожного користувача. На практиці ми використовуємо для GameFi проєктів версію з multiplier для врахування різних ваг стейку (наприклад, рідкісні NFT дають більше винагороди).

Процес та терміни

Починаємо з game economics документа: token flows, mint/burn механіки, projected supply schedule, sink analysis. До написання коду економіка моделюється (Cadence, Python simulation).

Як ми будуємо GameFi: 5 етапів

  1. Економічне моделювання — 1-2 тижні. Розробляємо dual-token модель, розраховуємо sink'и, прописуємо стимули для long-term holding.
  2. Розробка токен-контрактів — 2-3 тижні. ERC-20 для governance, ERC-20 для utility, з настроюваною політикою mint/burn.
  3. Смарт-контракти NFT — 3-5 тижнів. ERC-721 / ERC-1155 з dynamic metadata, breeding/crafting, Chainlink VRF.
  4. Staking + rewards — 2-3 тижні. Контракт на базі reward-per-share, інтерфейси для frontend.
  5. Маркетплейс (опціонально) — 2-4 тижні. Кастомний маркетплейс з enforced royalty.

Що входить у роботу

  • Вихідний код усіх смарт-контрактів з тестами (Foundry/Hardhat)
  • Документація архітектури та економіки
  • Інтеграція з Chainlink, Tenderly для моніторингу
  • Аудит коду та формальна верифікація (Slither, Mythril, Echidna)
  • Навчання команди роботі з контрактами
  • Підтримка після деплою (3 місяці)

Базовий GameFi стек (токени + NFT + staking + маркетплейс) — від 8 до 16 тижнів. Повна гра з on-chain рандомом, breeding, dynamic NFT — 4-8 місяців. ZK-based verifiable gameplay — окремий проєкт від 6 місяців.

Зв'яжіться з нами для аудиту вашої токеноміки — оцінимо ризики та доопрацюємо sink-механізми. Замовте розробку GameFi проєкту — отримайте готовий продукт з перевіреною економікою. Наш досвід — десятки реалізованих проєктів у Web3, включно з аудитом 15+ P2E ігор. Гарантуємо стабільність контрактів і прозорість коду. Сертифіковані аудитори перевіряють кожен контракт на типові вразливості (reentrancy, flash loan, oracle manipulation).