Разработка системы депозитов и выводов
Система депозитов и выводов — самая критичная с точки зрения безопасности часть криптобиржи. Здесь хранятся и перемещаются реальные средства пользователей. Каждая ошибка — прямые финансовые потери. Разберём архитектуру с акцентом на безопасность и устойчивость к сбоям.
Архитектура депозитов
Генерация депозитных адресов
Каждый пользователь получает уникальный депозитный адрес для каждой сети. Существуют два подхода:
HD Wallet (Hierarchical Deterministic). Из одного мастер-seed генерируется детерминированное дерево ключей по BIP-32/BIP-44. Для Bitcoin: m/44'/0'/0'/0/{user_index}. Для Ethereum: m/44'/60'/0'/0/{user_index}.
import { HDNodeWallet } from "ethers";
const masterNode = HDNodeWallet.fromMnemonic(Mnemonic.fromPhrase(MASTER_MNEMONIC));
function getDepositAddress(userId: number, coinIndex: number): string {
// m/44'/coinIndex'/0'/0/userId
const child = masterNode
.deriveChild(44 + 0x80000000) // purpose
.deriveChild(coinIndex + 0x80000000) // coin type
.deriveChild(0 + 0x80000000) // account
.deriveChild(0) // external
.deriveChild(userId);
return child.address;
}
Мастер-seed хранится в HSM (Hardware Security Module) или в зашифрованном vault (HashiCorp Vault). Публичные ключи для генерации адресов — в БД, приватные ключи для подписи — только в HSM.
Shared address + memo. Один адрес на всю биржу, пользователь указывает memo/tag при отправке. Используется для Ripple (XRP), Stellar (XLM), Cosmos. Дешевле в обслуживании, но ошибка в memo — потеря средств.
Мониторинг входящих транзакций
Сервис мониторинга подписывается на события блокчейна:
-
EVM chains:
eth_subscribe("logs", { address: [depositAddresses], topics: [ERC20_TRANSFER_TOPIC] })через WebSocket к ноде + pollingeth_getBlockByNumberкак fallback -
Bitcoin:
zmqpubrawtxот Bitcoin Core + периодическое сканирование адресов черезscantxoutset - TRON: TronGrid WebSocket или polling TronScan API
type DepositMonitor struct {
node EthClient
db *DB
confirmations int // минимум подтверждений
}
func (m *DepositMonitor) ProcessBlock(blockNum uint64) {
receipts := m.node.GetBlockReceipts(blockNum)
for _, receipt := range receipts {
for _, log := range receipt.Logs {
if !isERC20Transfer(log) {
continue
}
to := common.HexToAddress(log.Topics[2].Hex())
if !m.db.IsDepositAddress(to) {
continue
}
m.recordPendingDeposit(Deposit{
TxHash: receipt.TxHash,
BlockNum: blockNum,
UserAddress: to,
Token: log.Address,
Amount: new(big.Int).SetBytes(log.Data),
})
}
}
}
Подтверждения и finality
Количество требуемых подтверждений зависит от сети и суммы:
| Сеть | Минимум подтверждений | Причина |
|---|---|---|
| Bitcoin | 3–6 | Вероятность реорга |
| Ethereum | 12–20 (или finalized) | Post-merge finality |
| Polygon PoS | 100–256 | Checkpoint finality |
| BSC | 15–20 | PoSA, более централизован |
| TRON | 19 | Solid consensus |
| Solana | 32 (finalized) | Tower BFT |
После достижения порога подтверждений депозит зачисляется на баланс пользователя. До этого — в статусе pending. Отображаем пользователю pending депозиты с индикатором прогресса.
Reorg handling: хранить block_hash вместе с block_number. При обнаружении реорга (хэш блока изменился) — помечать затронутые транзакции как reorged и переначинать мониторинг.
Консолидация средств (sweeping)
Депозитные адреса — тысячи или миллионы. Хранить ETH на каждом адресе — дорого и небезопасно. Нужна автоматическая консолидация на master hot wallet:
func (s *Sweeper) SweepAddress(depositAddr Address) error {
balance, _ := s.node.GetBalance(depositAddr)
if balance.Cmp(s.minSweepAmount) < 0 {
return nil // не стоит газа
}
// Для ERC20: сначала нужно отправить ETH на газ
if s.token != ETH {
gasCost := estimateGas(depositAddr, HOT_WALLET, token)
s.fundGas(depositAddr, gasCost)
}
// Подписываем через HSM — приватный ключ depositAddr никогда не покидает HSM
tx := s.buildTransfer(depositAddr, HOT_WALLET, balance)
signed := s.hsm.Sign(depositAddr, tx)
return s.node.SendRawTransaction(signed)
}
Для ERC20 токенов задача осложняется: нужен ETH на газ на депозитном адресе. Решения:
- Gas station: отправлять ETH перед sweep, потом sweep токенов
- Gasless sweep через EIP-2612/permit: если токен поддерживает permit, биржа сама оплачивает газ
- Batch sweep через multicall: один вызов собирает средства с множества адресов
Архитектура выводов
Очередь и апрувы
Вывод проходит несколько стадий:
REQUESTED → VALIDATED → APPROVED → SIGNING → BROADCASTING → PENDING → CONFIRMED
VALIDATED: проверка баланса, лимитов, AML/KYC. Если прошёл — резервируем средства (вычитаем из available balance).
APPROVED: для крупных сумм — manual review оператором биржи или мультиподпись (M-of-N апрув от нескольких операторов). Для малых сумм — автоматический апрув.
SIGNING: подпись транзакции в HSM или cold wallet системе. Никогда не подписываем на сервере, где хранится логика апрувов — разделение обязанностей.
BROADCASTING: отправка транзакции в сеть. После этого транзакцию нельзя отменить (теоретически).
Gas management
Biржа должна оплачивать газ за выводы. Нужна система управления газом:
- Мониторинг
eth_gasPrice/ EIP-1559baseFee+maxPriorityFee - Бюджет на газ: учитываем стоимость газа в себестоимости вывода или в комиссии
- RBF (Replace By Fee) для застрявших Bitcoin транзакций
-
EIP-1559 bump: при застревании Ethereum транзакции — отправить с тем же nonce и увеличенным
maxFeePerGas
type GasTracker struct {
mu sync.Mutex
currentBase *big.Int
priorityFee *big.Int
}
func (g *GasTracker) RecommendGas(urgency string) GasParams {
g.mu.RLock()
defer g.mu.RUnlock()
multiplier := map[string]float64{
"slow": 1.0,
"normal": 1.2,
"fast": 1.5,
}[urgency]
return GasParams{
MaxFeePerGas: multiplyBig(g.currentBase, multiplier * 2),
MaxPriorityFeePerGas: multiplyBig(g.priorityFee, multiplier),
}
}
Нотификации и статусы
Пользователь должен видеть состояние вывода в реальном времени. Интеграция:
- WebSocket push при каждом изменении статуса
- Email/Telegram уведомления на ключевых этапах (апрув, отправка, подтверждение)
- Transaction hash с ссылкой на explorer сразу после broadcasting
Безопасность
Критические проверки
Whitelist адресов: требовать добавления нового адреса за 24–48 часов до возможности вывода на него. При добавлении — email подтверждение + 2FA. Предотвращает мгновенный вывод при компрометации аккаунта.
Anti-phishing: отображать anti-phishing код в письмах и UI (пользователь сам его устанавливает). Если его нет — подозрительно.
Лимиты вывода: дневные лимиты по уровням KYC. При превышении — manual review.
Velocity checks: несколько выводов за короткое время → временная блокировка и уведомление.
Hot/Warm/Cold wallet сегрегация
- Hot wallet: небольшой операционный запас (10–20% от дневного объёма выводов), всегда online, автоматические выводы
- Warm wallet: multi-sig (2-of-3 или 3-of-5 hardware keys), пополняет hot wallet раз в день
- Cold wallet: оффлайн хранение, только для крупных резервов, ручная процедура доступа
Распределение: 5–10% hot, 15–20% warm, 70–80% cold. Конкретные цифры зависят от объёмов и модели рисков биржи.
Мониторинг аномалий
type WithdrawalGuard struct {
limits map[UserID]DayLimits
velocities map[UserID][]time.Time
}
func (g *WithdrawalGuard) Check(req WithdrawalRequest) error {
// 1. Дневной лимит
if g.limits[req.UserID].Used + req.Amount > g.limits[req.UserID].Max {
return ErrDayLimitExceeded
}
// 2. Velocity: не более 5 выводов за 10 минут
recent := g.getRecentWithdrawals(req.UserID, 10*time.Minute)
if len(recent) >= 5 {
return ErrVelocityLimitExceeded
}
// 3. Новый адрес — только если прошло время карантина
if !g.isWhitelistedAddress(req.UserID, req.Address) {
return ErrAddressNotWhitelisted
}
return nil
}
Инфраструктура и надёжность
Нода vs API провайдер: собственная полная нода даёт надёжность и независимость. API провайдеры (Alchemy, QuickNode, Infura) — удобство, но зависимость от третьей стороны. Для production биржи: несколько провайдеров + собственная нода, failover автоматический.
Идемпотентность: каждый запрос на вывод имеет уникальный withdrawal_id. Повторная обработка одного ID не создаёт дубль транзакции. Критично для восстановления после сбоев.
Transaction monitoring: после broadcasting — периодическая проверка статуса транзакции. Если через N минут не в mempool — считаем дропнутой, отправляем заново с корректным nonce.
Сроки и стоимость
| Компонент | Срок |
|---|---|
| Ethereum + ERC20 депозиты/выводы | 4–6 недель |
| Bitcoin | 3–4 недели |
| TRON | 2–3 недели |
| Каждая дополнительная EVM-сеть | 1–2 недели |
| Мультивалютный hot wallet менеджмент | 3–4 недели |
| Admin dashboard для мониторинга | 2–3 недели |
Полная система для 5-7 сетей с HSM интеграцией, AML проверками и административным интерфейсом — 3–5 месяцев.







