Настройка приёма ставок в нескольких криптовалютах для казино
Приймати ставки в BTC, ETH, USDT, BNB, SOL та ще двадцяти токенах — технічно нетривіально. Кожна мережа має свою модель адресації, фіналізацію, комісії та швидкість. Якщо не спроектувати правильно, отримаєте або frozen funds (депозит не зачислен через помилку верифікації), або дірку в обліку (USDT на Ethereum та USDT на TRON — різні токени, різні мережи, але один рядок в базі).
Архітектура мультивалютного приёму ставок
HD-кошельки та унікальні адреси
Кожному користувачу потрібна унікальна адреса для кожної мережі — інакше не можна автоматично сопоставити входящий платіж з конкретним аккаунтом. Стандартний підхід: HD-кошелек (BIP-32/BIP-44) з деривацією адрес.
Master seed → деривація за шляхом m/44'/coin_type'/account'/0/index:
import { HDKey } from '@scure/bip32'
import { mnemonicToSeedSync } from '@scure/bip39'
const masterSeed = mnemonicToSeedSync(process.env.MASTER_MNEMONIC!)
const masterKey = HDKey.fromMasterSeed(masterSeed)
function deriveAddress(coinType: number, userId: number): string {
// BIP-44: m/44'/coinType'/0'/0/userId
const child = masterKey.derive(`m/44'/${coinType}'/0'/0/${userId}`)
// Для EVM-мереж coinType=60, Bitcoin=0, Solana=501
return toChecksumAddress(child.publicKey)
}
Для EVM-совместимих мереж (Ethereum, BNB Chain, Polygon, Arbitrum) — один адрес працює у всіх мережах, але це РІЗНІ баланси. Не змішуйте: користувач може відправити ETH на його BSC-адрес — монети будуть на іншій мережі та ніколи не зачисляться.
Мониторинг входящих транзакцій
Два підходи:
Webhook-based через платіжний провайдер (NOWPayments, CoinsPaid, Binance Pay API). Провайдер мониторит адреси та шле webhook при поступленні. Швидко інтегрувати, але: провайдер має доступ до ваших коштів, більш високі fees, менше контролю.
Self-hosted мониторинг — власний сервіс слухає ноди та індексує входящі транзакції. Повний контроль, нижчі затрати при обсягу, але: відповідальність за інфраструктуру.
// Мониторинг EVM адрес через eth_getLogs + transfer topic
const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
async function monitorIncomingTransfers(
addresses: string[],
fromBlock: number
) {
const logs = await client.getLogs({
fromBlock: BigInt(fromBlock),
toBlock: 'latest',
topics: [
TRANSFER_TOPIC,
null, // from: любой
addresses.map(padAddress), // to: наши адреса
],
})
for (const log of logs) {
const token = log.address // ERC-20 контракт
const to = unpadAddress(log.topics[2])
const amount = BigInt(log.data)
await processDeposit({ token, to, amount, txHash: log.transactionHash, blockNumber: log.blockNumber })
}
}
Фіналізація по мережах: Скільки підтверджень чекати
Головна помилка — зачислювати ставку після 1 підтвердження у всіх мережах. Реорги трапляються, і ставка буде зачисленою без реальних коштів.
| Мережа | Рекомендовані підтвердження | Приблизний час |
|---|---|---|
| Bitcoin | 3-6 | 30-60 хв |
| Ethereum | 12-15 (PoS safe) | 3-4 хв |
| BNB Chain | 15-20 | 60-90 сек |
| Polygon | 256 (checkpoint на ETH) | ~10 хв |
| Solana | 32 (finalized) | ~15 сек |
| TRON | 19 (SR solid) | ~60 сек |
| Arbitrum | 1 (L2 finality) | сек (soft), 7 днів (challenge) |
Розумний компроміс для ставок: Ethereum — 12 підтверджень, швидкі L1s (BSC, TRON) — 20 підтверджень, Solana — finalized status.
Облік мультивалютного балансу
База даних повинна розрізняти не просто "токен", а "токен + мережа":
CREATE TABLE user_balances (
user_id BIGINT NOT NULL,
network VARCHAR(50) NOT NULL, -- 'ethereum', 'bsc', 'tron'
token_address VARCHAR(100), -- NULL для native token
token_symbol VARCHAR(20) NOT NULL,
raw_amount NUMERIC(78, 0) NOT NULL, -- wei/lamports, без decimals
decimals SMALLINT NOT NULL,
PRIMARY KEY (user_id, network, COALESCE(token_address, 'native'))
);
-- USDT на різних мережах — РІЗНІ рядки
-- user_id=1, network='ethereum', token='0xdAC17F...', symbol='USDT'
-- user_id=1, network='tron', token='TR7NHqjeKQ...', symbol='USDT'
Ніколи не складайте raw_amount токенів з різними decimals без нормалізації. USDT = 6 decimals, більшість ERC-20 = 18 decimals.
Конвертація до єдиної валюти обліку
Казино потрібно рахувати GGR (Gross Gaming Revenue) у єдиній валюті (зазвичай USD або EUR). Для цього потрібен price feed:
// Фіксуємо USD-вартість на момент ставки
async function recordBet(userId: number, currency: string, network: string, rawAmount: bigint, decimals: number) {
const humanAmount = Number(rawAmount) / Math.pow(10, decimals)
const usdPrice = await priceOracle.getPrice(currency) // Chainlink, CoinGecko, Binance
const usdValue = humanAmount * usdPrice
await db.query(`
INSERT INTO bets (user_id, currency, network, raw_amount, decimals, usd_value_at_time, placed_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW())
`, [userId, currency, network, rawAmount.toString(), decimals, usdValue])
}
Фіксуйте USD-вартість у момент ставки — інакше PnL звіти залежатимуть від поточного курсу, що робить фінансову звітність непередбачуваною.
Безпека: Гарячі та холодні кошельки
Не тримайте всі кошти користувачів на hot wallet. Стандартна схема:
- Hot wallet — 5-10% від щоденного обороту. Миттєві виплати.
- Cold/warm wallet (multisig) — залишок. Sweep по розписанню.
Автоматичний sweep: як тільки баланс перевищує поріг, автоматично переводити на hot wallet агрегаційний адрес. Інакше кошти розкидані по тисячам адрес, неуправляємо.
Провайдери vs self-hosted
| Параметр | Провайдер (NOWPayments, CoinsPaid) | Self-hosted |
|---|---|---|
| Час інтеграції | 1-3 дні | 2-4 тижні |
| Комісія | 0.5-1% з обороту | Тільки газ |
| Контроль приватних ключів | У провайдера | У вас |
| Мультичейн | 30-200+ монет з коробки | Скільки реалізуєте |
| Кастомізація підтверджень | Обмежена | Повна |
Для невеликого казино (< $100k обороту/день) — провайдер обґрунтований. При зростанні обсягів комісія провайдера стає значною статтею витрат, self-hosted окупається за місяці.







