Настройка приєму ставок в Ethereum для казино
Ключова проблема інтеграції Ethereum в онлайн-казино — це не "як приймати ETH", це "як забезпечити честність та миттєвість при тому що блокчейн повільний та публічний". Клієнт натискає "поставити" — не може чекати 12 секунд підтвердження. А використовувати blockhash як джерело рандомності — це запрошення для майнерів маніпулювати результатом.
Архітектура для онлайн-казино
Два фундаментально різні підходи:
On-chain казино — смарт-контракт управляє всією логікою: прийняти ставку, визначити результат, виплатити. Повністю прозоро та trustless. Проблеми: latency (12 сек на блок), gas вартість кожної дії, обмеженість рандомності. Підходить для poker/blackjack з повільним темпом або slot machines через VRF.
Hybrid казино — депозити та виводи on-chain, ігрові сесії off-chain. Найпоширеніший підхід для casino: користувач депонує ETH/USDC в смарт-контракт, отримує внутрішні кредити, грає миттєво off-chain, виводить on-chain. Баланс доказується через Merkle proof або ZK proof.
Більшість практичних реалізацій — hybrid.
Смарт-контракт для депозитів та виводів
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract CasinoVault is ReentrancyGuard, Ownable {
mapping(address => uint256) public ethBalances;
mapping(address => mapping(address => uint256)) public tokenBalances;
// Авторизовані оператори для off-chain виплат
mapping(address => bool) public operators;
event Deposited(address indexed user, address indexed token, uint256 amount);
event Withdrawn(address indexed user, address indexed token, uint256 amount);
function depositETH() external payable {
require(msg.value > 0, "Zero deposit");
ethBalances[msg.sender] += msg.value;
emit Deposited(msg.sender, address(0), msg.value);
}
function depositToken(address token, uint256 amount) external nonReentrant {
IERC20(token).transferFrom(msg.sender, address(this), amount);
tokenBalances[msg.sender][token] += amount;
emit Deposited(msg.sender, token, amount);
}
// Вивід з підписом оператора (off-chain баланс верифікований)
function withdrawWithSignature(
address token,
uint256 amount,
uint256 nonce,
bytes calldata signature
) external nonReentrant {
bytes32 hash = keccak256(abi.encodePacked(
msg.sender, token, amount, nonce, address(this), block.chainid
));
bytes32 ethHash = MessageHashUtils.toEthSignedMessageHash(hash);
address signer = ECDSA.recover(ethHash, signature);
require(operators[signer], "Invalid operator signature");
require(!usedNonces[nonce], "Nonce used");
usedNonces[nonce] = true;
// виплата...
}
mapping(uint256 => bool) public usedNonces;
}
Паттерн operator signature дозволяє off-chain системі авторизувати виводи. Оператор підписує "користувач X має право вивести Y токенів" — не вимагає on-chain транзакції для кожного ігрового ходу.
Верифіцюємий рандом: Chainlink VRF v2.5
Для on-chain ігор (slots, dice, roulette) єдине надійне джерело рандомності на EVM — Chainlink VRF. Використання block.prevrandao (колишній blockhash) небезпечне: валідатори Ethereum можуть впливати на RANDAO значення.
import "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
contract DiceGame is VRFConsumerBaseV2Plus {
uint256 s_subscriptionId;
bytes32 s_keyHash = 0x787d74...; // VRF key hash для Ethereum mainnet
mapping(uint256 => address) public requestIdToPlayer;
mapping(uint256 => uint256) public requestIdToBet;
function rollDice(uint256 betAmount) external payable returns (uint256 requestId) {
require(msg.value >= betAmount, "Insufficient bet");
requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: 3, // очікуємо 3 блоки для безпеки
callbackGasLimit: 100000,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
requestIdToPlayer[requestId] = msg.sender;
requestIdToBet[requestId] = betAmount;
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords)
internal override
{
uint256 result = (randomWords[0] % 6) + 1; // 1-6
address player = requestIdToPlayer[requestId];
uint256 bet = requestIdToBet[requestId];
if (result >= 4) {
// виплата 2x
payable(player).transfer(bet * 2);
}
// інакше ставка залишається у контракті
}
}
VRF має latency ~1-2 блоки (12-24 сек). Для швидких ігор неприйнятно — потрібен hybrid підхід: гра продовжується off-chain, VRF використовується тільки для seed першоначального стану сесії.
Прийом USDC/USDT замість ETH
Більшість гравців віддають перевагу stablecoins — немає волатильності. Додати підтримку ERC-20 у vault контракт тривіально (див. depositToken вище). Важні нюанси:
USDT має комісію на transfer (fee-on-transfer) в теорії (хоча зараз 0%), потрібно перевіряти фактично отримане. Паттерн:
function depositToken(address token, uint256 amount) external {
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
IERC20(token).transferFrom(msg.sender, address(this), amount);
uint256 actualAmount = IERC20(token).balanceOf(address(this)) - balanceBefore;
tokenBalances[msg.sender][token] += actualAmount; // урахуємо реально отримане
}
Gas для ERC-20 транзакцій дорожче ETH transfers (~65K gas vs ~21K). На Ethereum mainnet це ~$1-3 при помірному gas. Для казино з дрібними ставками краще працювати на L2 (Base, Arbitrum) — gas в 50-100x дешевше.
L2-оптимізація
Оптимальна інфраструктура для казино в 2024 році — Base або Arbitrum One:
- Gas для депозиту/виводу: $0.01-0.10 (vs $2-10 на mainnet)
- Швидкість підтвердження: 1-2 сек (L2 фіналізація достатня для казино)
- USDC від Circle нативно доступний на Base та Arbitrum
Розгорнути контракт на Base — те ж саме що на Ethereum mainnet, просто змінюється --rpc-url у foundry deploy script.
Регуляторні аспекти
Смарт-контракт казино повинен підтримувати: геоблокування на рівні фронтенду (IP-фільтрація), можливість blacklist адрес (OFAC санкції), механізм паузи (emergency stop). Аудит контракту обов'язковий перед launch — уразливість у casino vault з ліквідністю означає повну втрату коштів.







