Представьте: вы хотите запустить онлайн-казино с 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 недель. Стоимость рассчитывается индивидуально после анализа требований. Закажите разработку — мы подготовим детальный план. Свяжитесь с нами для обсуждения вашего проекта.







