Уявіть: гравець ставить 0.1 ETH на червоне в блокчейн-рулетці на Polygon. Без верифікованого рандому він не може бути впевнений, що число не було визначено майнером або оператором. Chainlink VRF (Verifiable Random Function) — це децентралізований генератор випадкових чисел, який надає криптографічний доказ коректності кожного результату. На практиці ми виконали понад 15 інтеграцій VRF у проєкти на Ethereum, Polygon та BNB Chain для DeFi-казино, слотів і лотерей. Щоразу переконувалися: правильне налаштування параметрів — subscriptionId, keyHash, callbackGasLimit — критичне для надійності та захисту від затримок.
Хочете впровадити чесний рандом у своє казино? Зв'яжіться з нами — ми проаналізуємо проєкт і запропонуємо оптимальне рішення.
Основні проблеми при інтеграції VRF
Основні проблеми при інтеграції VRF — вибір режиму оплати, встановлення requestConfirmations, розрахунок callbackGasLimit. Помилка в будь-якому з цих параметрів призводить до втрати запиту або перевитрати газу. Далі ми розберемо кожне налаштування на прикладі реального коду.
Чому Chainlink VRF — стандарт для чесного рандому?
Процес генерації складається з трьох кроків:
- Контракт гри викликає
requestRandomWordsна Coordinator Chainlink. - Chainlink збирає seed з блоків і підписує його своїм ключем, формуючи доказ.
- Coordinator викликає
fulfillRandomWordsз результатом, контракт перевіряє доказ і обчислює підсумкове число.
VRF генерує випадкове число з криптографічним доказом його коректності. On-chain верифікація доказу відбувається до того, як число використовується в логіці гри. Маніпуляція виключена — ні оператор казино, ні гравці не можуть вплинути на результат. Згідно з Chainlink VRF v2.5 Documentation, кожен запит містить доказ, який верифікується on-chain.
VRF v2.5: підписка vs Direct Funding
Актуальна версія — VRF v2.5. Два режими оплати: Subscription model і Direct Funding.
| Параметр | Subscription | Direct Funding (VRFV2PlusWrapper) |
|---|---|---|
| Управління | Поповнення балансу в підписці | Автоматично з контракту |
| Гнучкість | Кілька контрактів на одному балансі | Кожен запит оплачується окремо |
| Вартість | Нижча при частих запитах | Трохи дорожче за запит |
| Ідеально для | Рулетка, слоти, карткові ігри | NFT mint, разові турніри |
Для high-load казино вибирайте subscription. Для проєктів зі змінним навантаженням — Direct Funding простіше операційно. При правильному налаштуванні subscription економія газу може досягати 25%.
Інтеграція в контракт казино
// 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 газу достатньо, для складної логіки — збільшуйте.
- requestConfirmations: 3 — мінімум. На Ethereum при реоргу в 2 блоки Chainlink може отримати інший seed. Для jackpot ставок ставте 5–10 підтверджень.
- Зберігайте ставки в mapping requestId => Bet. Масив ставок з пошуком — O(n) у колбеку, що веде до gas griefing.
Як уникнути втрати запитів VRF?
Chainlink надає кілька keyHash для однієї мережі — вони відрізняються максимальним gas price, який Chainlink готовий витратити на доставку random. На Ethereum mainnet:
| Lane | Max gas price | Затримка | Ціна |
|---|---|---|---|
| 200 gwei | 200 gwei | Можлива затримка при піках | Дешевше |
| 500 gwei | 500 gwei | Мінімальна | Дорожче |
Для казино з миттєвими іграми беріть 500 gwei lane — гравець не повинен чекати годинами при перевантаженні мережі. Вартість кожного запиту VRF при 500 gwei становить близько 0.00015 LINK.
Захист від зловживань та timeout
Ставка розміщена — скасування неможливе. Це правильно, оскільки random уже запрошено. Однак якщо random не прийшов протягом 24 годин (проблеми з Coordinator або баланс підписки вичерпано), необхідна функція повернення:
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); } Тестування з Foundry
Chainlink надає VRFCoordinatorV2_5Mock для локальних тестів. Вручну викликайте fulfillRandomWords із заданим random:
vrfCoordinator.fulfillRandomWords(requestId, address(game)); Fuzz-тест перевіряє виплати для всіх значень randomWords[0] від 0 до 2^256-1. Граничний випадок: randomWords[0] % 37 == 0 — зеро на рулетці. Ваш контракт повинен коректно обробляти цей edge case. Ми також використовуємо forge fuzz з більш ніж 10 000 ітерацій для виявлення прихованих багів.
Що входить в роботу з інтеграції VRF
- Аудит поточного контракту та аналіз ігрової механіки.
- Проектування архітектури: вибір Subscription або Direct Funding.
- Реалізація інтеграції з VRF v2.5 (Solidity, Foundry).
- Розробка захистів: timeout refund, requestConfirmations.
- Тестування на Sepolia з реальним VRF, включаючи fuzz-тести.
- Документація коду та інструкція з деплою.
- Передача доступу до репозиторію та підтримка протягом місяця.
Процес роботи
- Аналітика — обговорюємо механіку гри, частоту запитів, вимоги до чесності.
- Проектування — вибираємо режим VRF, keyHash, requestConfirmations.
- Реалізація — пишемо контракт з інтеграцією, підключаємо тести.
- Тестування — на тестовій мережі (Sepolia) перевіряємо роботу з реальним VRF.
- Деплой — розгортаємо в mainnet з налаштуванням підписки або wrapper.
Строки
- Базова інтеграція VRF в існуючий контракт: 1–2 дні.
- Новий контракт казино з VRF, виплатами та захистами: 2–3 дні.
- Тестування на Sepolia з реальним VRF — включено.
Вартість розраховується після уточнення типу гри та механіки ставок. Для оцінки напишіть нам — ми проаналізуємо проєкт і запропонуємо оптимальне рішення.
Типові помилки при інтеграції VRF
- Недостатній callbackGasLimit — запит не виконується.
- Занадто мале requestConfirmations — вразливість до реоргу.
- Відсутність функції timeout — кошти зависають при збої Coordinator.
- Використання масиву ставок замість mapping — перевитрата газу.
Впровадьте верифікований рандом у ваше казино — зв'яжіться з нами для консультації. Замовте розробку смарт-контракту з VRF під ключ.







