Уявіть: гравець ставить 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 під ключ.







