Разработка горячего кошелька биржи

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка горячего кошелька биржи
Сложная
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка горячего кошелька биржи

Горячий кошелёк биржи — это онлайн-система хранения криптовалюты, которая автоматически обрабатывает выводы пользователей. Он всегда подключён к интернету, поэтому представляет основной вектор атаки. Компромисс между удобством и безопасностью решается через правильную архитектуру: минимальные остатки на горячем, основной резерв — на холодном.

Архитектура горячего кошелька

HD Wallet для Ethereum и EVM-сетей

Горячий кошелёк — это master hot wallet address, куда консолидируются средства с депозитных адресов. Для Ethereum-based сетей — один адрес на всю биржу или несколько для параллельной обработки выводов.

type HotWallet struct {
    address    common.Address
    keyManager KeyManager        // абстракция над HSM или keystore
    client     *ethclient.Client
    nonceTrack *NonceTracker     // управление nonce
    gasTrack   *GasTracker
}

// NonceTracker — критический компонент
// PostgreSQL хранит последний использованный nonce
// При параллельных выводах нужна атомарная выдача nonce
type NonceTracker struct {
    db   *DB
    mu   sync.Mutex
    pool chan uint64  // pre-fetched nonce pool
}

func (nt *NonceTracker) Next(ctx context.Context) (uint64, error) {
    nt.mu.Lock()
    defer nt.mu.Unlock()
    
    // Получаем следующий nonce атомарно
    var nonce uint64
    err := nt.db.QueryRow(ctx, 
        "UPDATE hot_wallet SET nonce = nonce + 1 RETURNING nonce - 1"
    ).Scan(&nonce)
    return nonce, err
}

Управление nonce — одна из главных технических сложностей горячего кошелька. При параллельных транзакциях нужно гарантировать, что два воркера не получат один nonce. Решение: атомарный инкремент в БД с mutex-защитой.

Мультивалютный горячий кошелёк

Разные активы требуют разных подходов:

Сеть Тип кошелька Особенности
Ethereum / EVM EOA или Smart Contract Wallet ERC-20 через transfer/transferFrom
Bitcoin P2WPKH (SegWit) UTXO модель, coin selection
Tron TRX/TRC-20 Bandwidth/Energy модель
Solana ED25519 keypair SPL tokens, compute units
Ripple Shared address + destination tag Tag обязателен
# Bitcoin горячий кошелёк — UTXO coin selection
from bitcoinlib.wallets import Wallet

class BitcoinHotWallet:
    def __init__(self, wallet_name: str):
        self.wallet = Wallet(wallet_name)
    
    def create_withdrawal(self, to_address: str, amount_sat: int, fee_rate: int):
        # Coin selection — выбираем минимальное количество UTXO
        # для покрытия суммы + комиссии
        utxos = self.wallet.utxos()
        selected, change = self.select_coins(utxos, amount_sat, fee_rate)
        
        tx = self.wallet.transaction_create(
            outputs=[(to_address, amount_sat)],
            utxos=selected,
            fee=self.estimate_fee(len(selected), fee_rate),
            replace_by_fee=True,  # RBF для возможности bump fee
        )
        return tx
    
    def select_coins(self, utxos, amount, fee_rate):
        # Простая стратегия: smallest first (минимизирует UTXO set)
        utxos.sort(key=lambda u: u.value)
        selected = []
        total = 0
        for utxo in utxos:
            selected.append(utxo)
            total += utxo.value
            fee = self.estimate_fee(len(selected), fee_rate)
            if total >= amount + fee:
                change = total - amount - fee
                return selected, change
        raise InsufficientFunds()

Безопасность горячего кошелька

HSM интеграция

Приватный ключ горячего кошелька никогда не должен быть в plain text в памяти сервера. Уровни защиты:

Уровень 1 (минимальный): Зашифрованный keystore на диске (AES-256), пароль из environment variable или secrets manager (AWS Secrets Manager, HashiCorp Vault). Ключ дешифруется при старте сервиса и хранится в памяти.

Уровень 2 (рекомендуемый): HashiCorp Vault Transit Secrets Engine. Ключ никогда не покидает Vault — сервер посылает данные на подпись, получает подписанную транзакцию обратно.

import vault "github.com/hashicorp/vault/api"

type VaultSigner struct {
    client  *vault.Client
    keyName string
}

func (vs *VaultSigner) SignTransaction(txHash []byte) ([]byte, error) {
    // Vault Transit: подпись данных без выгрузки ключа
    path := fmt.Sprintf("transit/sign/%s", vs.keyName)
    secret, err := vs.client.Logical().Write(path, map[string]interface{}{
        "input":                base64.StdEncoding.EncodeToString(txHash),
        "hash_algorithm":       "sha2-256",
        "signature_algorithm":  "pkcs1v15",
        "prehashed":            true,
    })
    // Парсим ECDSA подпись и преобразуем в Ethereum формат
    return parseVaultSignature(secret.Data["signature"].(string))
}

Уровень 3 (максимальный): Hardware HSM (Thales Luna, AWS CloudHSM, YubiHSM). Подпись выполняется в аппаратном чипе, приватный ключ физически не извлекаем. Стоимость — от $20,000/год для dedicated HSM. Для большинства бирж Vault — оптимальный компромисс.

Лимиты и контроль

type WithdrawalLimiter struct {
    db    *DB
    cache *redis.Client
}

// Дневной лимит автоматических выводов из горячего кошелька
func (wl *WithdrawalLimiter) CheckAndConsume(amount decimal.Decimal, currency string) error {
    key := fmt.Sprintf("hot_wallet:daily_limit:%s:%s", currency, today())
    
    // Атомарный инкремент с проверкой лимита
    script := redis.NewScript(`
        local current = redis.call('GET', KEYS[1])
        if current == false then current = 0 end
        local new_val = tonumber(current) + tonumber(ARGV[1])
        if new_val > tonumber(ARGV[2]) then
            return redis.error_reply("LIMIT_EXCEEDED")
        end
        redis.call('SETEX', KEYS[1], 86400, new_val)
        return new_val
    `)
    
    _, err := script.Run(context.Background(), wl.cache, 
                         []string{key}, amount.String(), DAILY_LIMIT.String()).Result()
    return err
}

При превышении лимита — вывод встаёт в очередь и требует ручного апрува оператора (пополнение из warm wallet).

Управление балансом и пополнение

Hot wallet balance monitoring

type BalanceWatcher struct {
    hotWallet *HotWallet
    threshold decimal.Decimal  // минимальный порог
    target    decimal.Decimal  // целевой баланс после пополнения
    alerter   Alerter
}

func (bw *BalanceWatcher) Watch(ctx context.Context) {
    ticker := time.NewTicker(5 * time.Minute)
    for {
        select {
        case <-ticker.C:
            balance := bw.hotWallet.GetBalance()
            if balance.LessThan(bw.threshold) {
                bw.alerter.Alert(Alert{
                    Level:   "CRITICAL",
                    Message: fmt.Sprintf("Hot wallet balance low: %s. Need topup from warm wallet", balance),
                })
                // Автоматическое пополнение из warm wallet (если настроено)
                bw.requestTopup(bw.target.Sub(balance))
            }
        case <-ctx.Done():
            return
        }
    }
}

Консолидация ERC-20 токенов

Депозитные адреса накапливают токены. Sweep-процесс их консолидирует:

func (hw *HotWallet) SweepERC20(depositAddr common.Address, token common.Address) error {
    tokenContract := NewERC20(token, hw.client)
    balance, _ := tokenContract.BalanceOf(depositAddr)
    
    if balance.Cmp(MinSweepAmount) < 0 {
        return nil // не стоит газа
    }
    
    // Сначала нужен ETH для газа на depositAddr
    gasCost := hw.estimateSweepGas(depositAddr, token)
    if ethBalance := hw.getETHBalance(depositAddr); ethBalance.Cmp(gasCost) < 0 {
        // Отправляем ETH с hot wallet на депозитный адрес
        err := hw.sendETH(depositAddr, gasCost)
        if err != nil { return err }
        // Ждём подтверждения
        time.Sleep(15 * time.Second)
    }
    
    // Теперь sweep токенов с депозитного адреса на hot wallet
    nonce, _ := hw.nonceTrack.NextForAddress(depositAddr)
    tx := hw.buildERC20Transfer(depositAddr, hw.address, token, balance, nonce)
    signed := hw.keyManager.Sign(depositAddr, tx)
    return hw.client.SendTransaction(signed)
}

Transaction management

Stuck transaction handling

Транзакция может зависнуть в mempool при недостаточном gas price. Система должна автоматически бампировать комиссию:

func (hw *HotWallet) BumpFee(txHash common.Hash) error {
    // Получаем оригинальную транзакцию
    origTx, _, _ := hw.client.TransactionByHash(txHash)
    
    // EIP-1559: увеличиваем maxFeePerGas и maxPriorityFeePerGas на 10%
    newMaxFee := new(big.Int).Mul(origTx.GasFeeCap(), big.NewInt(110))
    newMaxFee.Div(newMaxFee, big.NewInt(100))
    
    newPriorityFee := new(big.Int).Mul(origTx.GasTipCap(), big.NewInt(110))
    newPriorityFee.Div(newPriorityFee, big.NewInt(100))
    
    // Создаём замещающую транзакцию с тем же nonce
    replaceTx := types.NewTx(&types.DynamicFeeTx{
        Nonce:     origTx.Nonce(),
        To:        origTx.To(),
        Value:     origTx.Value(),
        Data:      origTx.Data(),
        Gas:       origTx.Gas(),
        GasFeeCap: newMaxFee,
        GasTipCap: newPriorityFee,
    })
    
    signed := hw.keyManager.Sign(replaceTx)
    return hw.client.SendTransaction(signed)
}

Транзакционный журнал

Каждая транзакция горячего кошелька логируется:

CREATE TABLE hot_wallet_transactions (
    id              BIGSERIAL PRIMARY KEY,
    tx_hash         VARCHAR(66),
    network         VARCHAR(20) NOT NULL,
    from_address    VARCHAR(42) NOT NULL,
    to_address      VARCHAR(42) NOT NULL,
    token           VARCHAR(42),
    amount          NUMERIC(36,18) NOT NULL,
    gas_price       NUMERIC(36,0),
    gas_used        INTEGER,
    status          VARCHAR(20) NOT NULL, -- pending/confirmed/failed/replaced
    withdrawal_id   BIGINT REFERENCES withdrawals(id),
    nonce           INTEGER,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    confirmed_at    TIMESTAMPTZ,
    block_number    BIGINT
);

Мониторинг

Горячий кошелёк требует 24/7 мониторинга:

  • Balanace alerts: при падении ниже порога
  • Failed transaction alerts: если транзакция не подтвердилась через N минут
  • Nonce gap detection: если в nonce sequence есть пропуск — что-то пошло не так
  • Unusual outflow: резкий рост объёма выводов — потенциальная атака

Grafana + Prometheus для метрик, PagerDuty или аналог для on-call алертов.

Сроки разработки

Компонент Срок
ETH/ERC-20 hot wallet 3–4 недели
Bitcoin UTXO wallet 3–4 недели
HSM/Vault интеграция 1–2 недели
Sweep automation 2–3 недели
Monitoring dashboard 1–2 недели
Тестирование на testnet 2–3 недели

Полный мультивалютный горячий кошелёк с HSM — 3–4 месяца.