Уявіть: ви хочете запустити онлайн-казино з Blackjack на блокчейні. Проблема — як забезпечити випадковість карт, не довіряючи централізованому серверу? В off-chain казино сервер здає карти, але гравець не бачить колоду. On-chain все прозоро — будь-хто може прочитати storage контракту. Якщо ми заздалегідь згенеруємо deck і збережемо, гравець побачить усі майбутні карти. Рішення — commit-reveal з VRF або Mental Poker. Ми використовуємо Chainlink VRF, але з оптимізацією: генеруємо весь deck одним запитом, а потім карти відкриваються поетапно без додаткових VRF. Наша команда з понад 7 років досвіду в Web3 реалізувала 20+ блокчейн-ігор та DeFi-проектів. Зв'яжіться з нами, щоб отримати консультацію з архітектури та аудиту.
Як Chainlink VRF забезпечує випадковість?
Chainlink VRF генерує верифіковане випадкове число off-chain із криптографічним доказом, яке перевіряється on-chain. Ніхто — ні гравець, ні оператор — не може передбачити результат. Це в 10 разів надійніше, ніж зберігання seed на контракті.
Флоу для Blackjack:
- Гравець робить ставку, контракт запитує VRF
- VRF fulfillment (через callback) — контракт отримує random, генерує deck або перші карти
- Гравець вирішує: hit або stand
- Якщо hit — новий VRF запит для наступної карти
Проблема з "якщо hit": кожен VRF запит — затримка (1-3 блоки) та додатковий LINK. Для реалтайм-гри некомфортно.
Оптимізація: запитати весь deck одразу — блокчейн казино blackjack
contract Blackjack is VRFConsumerBaseV2Plus {
struct Game {
address player;
uint256 bet;
uint8[] deck; // всі 52 карти в зашифрованому порядку
uint8 playerIdx; // поточний індекс в декі
uint8 dealerIdx;
bool active;
}
mapping(uint256 => Game) public games; // requestId -> game
mapping(address => uint256) public playerGame; // player -> gameId
function startGame() external payable {
require(msg.value >= MIN_BET, "Below minimum bet");
uint256 requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: KEY_HASH,
subId: subscriptionId,
requestConfirmations: 3,
callbackGasLimit: 300000,
numWords: 1, // одне велике число для shuffle
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
games[requestId] = Game({
player: msg.sender,
bet: msg.value,
deck: new uint8[](0),
playerIdx: 0,
dealerIdx: 4, // дилер бере карти з позиції 4
active: false // стане true після fulfillment
});
playerGame[msg.sender] = requestId;
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
Game storage game = games[requestId];
// Fisher-Yates shuffle детермінований з одного seed
uint8[52] memory deck;
for (uint8 i = 0; i < 52; i++) deck[i] = i;
uint256 seed = randomWords[0];
for (uint8 i = 51; i > 0; i--) {
seed = uint256(keccak256(abi.encodePacked(seed)));
uint8 j = uint8(seed % (i + 1));
(deck[i], deck[j]) = (deck[j], deck[i]);
}
// Перші 4 карти одразу здаються: player, dealer, player, dealer
game.deck = new uint8[](52);
for (uint8 i = 0; i < 52; i++) game.deck[i] = deck[i];
game.active = true;
emit GameStarted(requestId, game.player, deck[0], deck[2]); // видимі карти гравця
// deck[1] і deck[3] - карти дилера, deck[1] прихована до кінця
}
}
Після fulfillment deck зашафлений і зафіксований. Наступні ходи (hit) беруть карти з вже згенерованого deck — без нових VRF запитів. Швидко і дешево.
Чому deck не можна зберігати відкрито?
Але весь deck зберігається в game.deck — публічно! Технічно гравець може прочитати всі майбутні карти з storage.
Рішення: зберігати тільки seed, карти обчислювати детерміновано тільки коли вони «відкриваються»:
// Не храним deck, только seed
mapping(uint256 => uint256) private gameSeeds;
function getCard(uint256 gameId, uint8 position) private view returns (uint8) {
// Детерминированно вычисляем карту из seed и позиции
// Карта не в storage — нельзя прочитать заранее
return uint8(uint256(keccak256(abi.encodePacked(gameSeeds[gameId], position))) % 52);
}
Це не повністю вирішує проблему: технічно можна симулювати getCard для всіх позицій в тому ж блоці. Повне рішення — Mental Poker протокол з шифруванням кожної карти, але це значно складніше.
Як реалізувати логіку Blackjack в смарт-контракті?
Значення карт: туз = 1 або 11, картинки = 10, інші за номіналом:
function cardValue(uint8 card) internal pure returns (uint8) {
uint8 rank = card % 13; // 0-12: туз, 2-10, валет, дама, король
if (rank == 0) return 11; // туз (м'яке значення)
if (rank >= 10) return 10; // картинки
return rank + 1;
}
function handScore(uint8[] memory cards) internal pure returns (uint8) {
uint8 score = 0;
uint8 aces = 0;
for (uint i = 0; i < cards.length; i++) {
uint8 val = cardValue(cards[i]);
if (val == 11) aces++;
score += val;
}
// М'який туз стає жорстким (1) якщо bust
while (score > 21 && aces > 0) {
score -= 10;
aces--;
}
return score;
}
Логіка дилера on-chain: дилер бере карти поки score < 17, зупиняється на 17+ (включаючи soft 17 залежно від правил).
Як управляти банкролом за допомогою Kelly Criterion?
Контракт повинен мати ETH для виплат. House edge в Blackjack ~0.5% при оптимальній стратегії — це реальна маржа. Але дисперсія висока, потрібен достатній bankroll.
Kelly Criterion для максимальної ставки: при edge e та bankroll B, максимальна ставка ≈ B * e / variance. Для Blackjack з edge 0.5% та дисперсією ~1.3 — максимальна ставка ≈ 0.38% від bankroll. На практиці: обмежити максимальну ставку на рівні 1-2% bankroll.
uint256 public constant MAX_BET_PERCENT = 200; // 2% = 200/10000
function maxBet() public view returns (uint256) {
return address(this).balance * MAX_BET_PERCENT / 10000;
}
modifier validBet() {
require(msg.value >= MIN_BET && msg.value <= maxBet(), "Invalid bet");
_;
}
Чому UX критичний для блокчейн-блэкджека?
Блокчейн Blackjack потребує ретельного UX для асинхронності. VRF fulfillment — не миттєвий. Гравець натиснув "Deal" — чекає 1-3 блоки до появи карт.
Флоу в UI:
- Ставка →
startGame()→ статус "Dealing..." (чекаємо подіюGameStarted) - Карти з'являються → гравець бачить свої 2 карти, одну карту дилера
- Hit/Stand → миттєві, із зашафленого deck
- Stand → дилер відкриває карти → підсумок → виплата
Для polling подій: wagmi з useWatchContractEvent або WebSocket підключення до ноди.
Стек
| Компонент | Технологія |
|---|---|
| Smart contract | Solidity 0.8.x + VRF v2.5 |
| Тестування | Foundry + VRF mock |
| Frontend | React + wagmi + viem |
| Мережа | Polygon / Arbitrum (низький gas) |
| Аудит | Обов'язковий (gambling + custody коштів) |
Аудит контракту: критична безпека
Контракт зберігає ETH та виплачує виграші — помилки в логіці виплат або генерації випадковості ведуть до прямих втрат. Ми використовуємо Slither та Mythril для статичного аналізу, а також рекомендуємо зовнішній аудит у партнерів. Ваша безпека — наша гарантія. Детальніше про аудит смарт-контрактів можна дізнатися в документації Chainlink.
Що входить в розробку блокчейн-блэкджека?
- Архітектурна документація та вибір протоколу (VRF vs Mental Poker)
- Смарт-контракт Blackjack з логікою гри та управлінням банкролом
- Тести на Foundry (unit, fuzz, інтеграційні)
- Інтеграція Chainlink VRF (v2.5) з підпискою
- Frontend на React + wagmi + viem з підтримкою гаманців (MetaMask, WalletConnect)
- Деплой в тестову та основну мережу (Polygon/Arbitrum за вашим вибором)
- Код-рев'ю та фікси зауважень
- Навчання вашої команди роботі з контрактом
- Технічна підтримка на етапі запуску
Порівняння: VRF vs Mental Poker
| Критерій | Chainlink VRF | Mental Poker |
|---|---|---|
| Складність реалізації | Низька | Висока (криптографія) |
| Швидкість | 1-3 блоки | 1 раунд (миттєво) |
| Вартість газу | ~300k gas + LINK | ~500k gas (шифрування) |
| Довіра | Потрібна довіра до оракула | Повністю децентралізовано |
| Рекомендація | Для більшості проектів | Для проектів з максимальною децентралізацією |
Mental Poker: як це працює?
Mental Poker — криптографічний протокол, що дозволяє гравцям роздавати карти без довіреної третьої сторони. Кожна карта шифрується спільним ключем, потім перетасовується і розшифровується. Це забезпечує максимальну децентралізацію, але потребує більше газу та складніший у реалізації.Терміни орієнтовно
Робочий on-chain Blackjack (смарт-контракт, тести, базовий frontend): від 4 до 5 тижнів. З повним UI, анімаціями, статистикою та мобільною адаптацією: від 8 до 10 тижнів. Вартість розраховується індивідуально після аналізу вимог. Замовте розробку — ми підготуємо детальний план. Зв'яжіться з нами для обговорення вашого проекту.







