Интеграция Chainlink VRF для генерации случайных чисел в казино

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Интеграция Chainlink VRF для генерации случайных чисел в казино
Простая
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Интеграция Chainlink VRF для генерации случайных чисел в казино

Блокчейн-казино с честным рандомом — это не маркетинговое заявление, а техническая архитектура. Chainlink VRF (Verifiable Random Function) генерирует случайное число с криптографическим доказательством его корректности. On-chain верификация доказательства происходит до того, как число используется в логике игры — манипуляция исключена математически, а не организационно.

VRF v2.5: подписка vs Direct Funding

Актуальная версия — VRF v2.5. Два режима оплаты:

Subscription model. Создаётся подписка на vrf.chain.link, пополняется LINK. Несколько контрактов-казино используют один баланс. Подходит для продуктов с регулярными запросами — рулетка, слоты, карточные игры.

Direct Funding (VRFV2PlusWrapper). Контракт сам оплачивает каждый запрос LINK или нативным токеном (ETH, MATIC). Проще для запуска, нет нужды управлять отдельной подпиской. Но каждый запрос чуть дороже.

Для казино с высокой нагрузкой — subscription. Для NFT mint с рандомными атрибутами или разовых турниров — Direct Funding проще операционно.

Интеграция в контракт казино

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

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 immutable subscriptionId;
    bytes32 public immutable keyHash;

    struct Bet {
        address player;
        uint256 amount;
        uint8 betType;   // 0=red, 1=black, 2=number
        uint8 number;
    }

    mapping(uint256 requestId => Bet) public pendingBets;

    event BetPlaced(uint256 indexed requestId, address indexed player);
    event GameResult(uint256 indexed requestId, uint8 result, bool won);

    function placeBet(uint8 betType, uint8 number) external payable {
        require(msg.value >= MIN_BET, "Below minimum");
        require(msg.value <= MAX_BET, "Above maximum");

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

        pendingBets[requestId] = Bet({
            player: msg.sender,
            amount: msg.value,
            betType: betType,
            number: number
        });

        emit BetPlaced(requestId, msg.sender);
    }

    function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords)
        internal override {
        Bet memory bet = pendingBets[requestId];
        delete pendingBets[requestId];

        uint8 result = uint8(randomWords[0] % 37); // 0-36
        bool won = _checkWin(bet, result);

        if (won) {
            uint256 payout = _calculatePayout(bet);
            payable(bet.player).transfer(payout);
        }

        emit GameResult(requestId, result, won);
    }
}

Критичные детали

callbackGasLimit должен быть с запасом. Если газа не хватает в fulfillRandomWords — Chainlink не повторит вызов автоматически. Запрос будет потерян, ставка зависнет. Считайте реальный газ: forge test --gas-report. Для рулетки с выплатой и событиями — 150K газа достаточно. Для сложной логики с multiple bets — увеличивайте.

requestConfirmations: 3 — минимум. На Ethereum при реорге в 2 блока Chainlink может получить другой seed для random. 3 подтверждения — разумный компромисс между скоростью и безопасностью. Для jackpot ставок ставьте 5-10.

Не храните ставки в массиве. Mapping requestId => Bet правильный подход. Массив ставок с поиском по requestId — O(n) в коллбэке, gas griefing при большом числе pending ставок.

keyHash — выбор lane

Chainlink предоставляет несколько keyHash для одной сети — они отличаются максимальным gas price, который Chainlink готов потратить на deliver random. На Ethereum mainnet:

  • 0x8af... — 200 gwei lane: Chainlink задержит delivery если gas price выше
  • 0x9fe... — 500 gwei lane: доставка даже при перегрузке сети (дороже)

Для казино с мгновенными играми берите 500 gwei lane — игрок не должен ждать часами при пиковой нагрузке.

Защита от злоупотреблений

Ставка размещена, random в пути. Что если игрок размещает ставку и ждёт результата на другом устройстве, потом отменяет если результат ему не понравится? В текущей архитектуре отмена невозможна — средства заблокированы в контракте до fulfillRandomWords. Это правильно.

Но что если random не пришёл в течение 24 часов (Chainlink недоступен, баланс подписки исчерпан)? Нужна функция возврата ставки с проверкой timeout:

function refundExpiredBet(uint256 requestId) external {
    Bet memory bet = pendingBets[requestId];
    require(bet.player == msg.sender, "Not your bet");
    require(block.timestamp > betTimestamps[requestId] + 24 hours, "Not expired");
    delete pendingBets[requestId];
    payable(msg.sender).transfer(bet.amount);
}

Тестирование

Chainlink предоставляет VRFCoordinatorV2_5Mock для Foundry тестов — имитирует Coordinator и позволяет вручную вызвать fulfillRandomWords с заданным random:

vrfCoordinator.fulfillRandomWords(requestId, address(game));

Fuzz тест: проверяем корректность выплат при всех значениях randomWords[0] от 0 до 2^256-1. Граничный случай: randomWords[0] % 37 == 0 — зеро на рулетке, редкий edge case.

Сроки

Базовая интеграция VRF в существующий контракт игры: 1-2 дня. Новый контракт казино с VRF, выплатами, и защитой от timeout: 2-3 дня. Тестирование на Sepolia с реальным VRF — включено.

Стоимость рассчитывается после уточнения типа игры и механики ставок.