Кейс · TON / crypto-инфраструктура · 2026

Кошелёк на один показ

Два отдельных сервиса — тестовый и боевой — генерируют TON-кошельки: 24-словная мнемоника, ed25519-пара, адрес V5R1. Сид-фраза попадает в ответ ровно один раз за всю жизнь кошелька и никогда больше не появляется — ни в API, ни в панели, ни в базе.

2сервиса — testnet и mainnet
24слова мнемоники
1показ сид-фразы
15минут окно раскрытия
0общих секретов между сервисами

Задача

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

Решение — два физически разных сервиса на соседних доменах с одинаковой архитектурой, но без единого разделяемого секрета: ton.ifreework.com для тестов и mainnet.ifreework.com для боевых кошельков. Компрометация одного не открывает доступ ко второму.

Путь одного кошелька

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

  1. Подписанный запрос

    Каждый вызов несёт HMAC-SHA256 подпись по канонической строке (метод, путь, время, nonce, хэш тела) и обязательный Idempotency-Key — повтор того же ключа возвращает уже созданный кошелёк, а не плодит второй.

    X-Signature · X-Nonce · X-Timestamp
  2. Постановка в очередь

    POST отвечает мгновенно, ничего не ожидая: кошелька ещё нет, есть только id со статусом pending и адрес для опроса.

    202 Accepted · queue: ton-wallets
  3. Генерация в Node-сайдкаре

    Отдельный воркер очереди вызывает Node-сервис с TON SDK — вся криптография изолирована туда, потому что TON SDK написан на JavaScript, а не на PHP. Сайдкар слушает только внутреннюю docker-сеть и требует общий токен.

    Node 22 · @ton/crypto · @ton/ton
  4. Опрос статуса

    Клиент опрашивает тот же id, пока статус не станет ready или failed — обычно меньше секунды. Кошелёк из чужого запроса на этот id недоступен: чужой id возвращает такой же 404, как и несуществующий.

    GET /api/.../{id}
  5. Раскрытие — и всё

    Мнемоника есть только в первом успешном ответе на опрос. Следующий запрос — хоть через секунду — вернёт тот же кошелёк с mnemonic: null. Не забрали за 15 минут — фраза стирается из временного кэша безвозвратно, адрес остаётся, ключ — нет.

    RevealCache · TON_REVEAL_TTL

Testnet и mainnet — близнецы без общих секретов

Боевой сервис — не режим тестового, а полный клон с перепрошитой сетью: свой контейнер, своя база, свой сайдкар, свой домен. Ничего не переиспользуется.

ton.ifreework.com — testnet

  • Глобальный id сети -3, зашит в сайдкаре намертво
  • Адреса начинаются с 0Q… / kQ…
  • Хранение мнемоники: по умолчанию не хранится, опционально — зашифровано
  • Очистка просроченных записей — раз в 5 минут, хранение до года

mainnet.ifreework.com — боевой

  • Глобальный id сети -239, зашит в сайдкаре намертво
  • Адреса начинаются с UQ… / EQ…
  • Хранение мнемоники запрещено на уровне конфигурации — не просто выключено по умолчанию
  • Очистка просроченных записей — раз в минуту, хранение 180 дней

Клиент на каждой стороне ещё и сам себя перепроверяет: если ответ вдруг содержит адрес не того формата — UQ… там, где должно быть только 0Q…, — сервис отказывается его принять, а не молча продолжает работу с смешанными сетями.

Инцидент: как сайт клали пять воркеров

Настоящая причина — синхронный вызов Node-сайдкара внутри php-fpm-воркера при пуле в 5 воркеров: один медленный запрос вместе со сканер-ботом, долбившим сайт в это же время, забрал все воркеры разом — и лёг весь сайт, включая создание кошельков.

Вскоре после первого деплоя создание кошельков начало падать. Логи молчали — но не потому, что всё было в порядке: директива access_log $ton_log_path … в конфиге nginx проверяет на буквальный текст директивы, а не на то, во что раскрывается переменная, поэтому nginx подставлял перед каждым путём /etc/nginx/ и не мог никуда писать. Логирование молча ломалось с момента деплоя и маскировало настоящую проблему.

Настоящей проблемой был синхронный вызов сайдкара внутри веб-запроса при дефолтном pm.max_children: 5. Исправление — не просто увеличить пул, а убрать генерацию из пути веб-запроса: она переехала в отдельный процесс очереди, который нельзя истощить веб-трафиком, независимо от размера пула php-fpm.

Что держит систему на ногах

  • Сид-фраза физически не может попасть в логи — объект секрета ничего не хранит в своих полях, отдельный обработчик логов вычищает канал, а API-эндпоинт не логирует тело запроса вовсе.
  • Нет истории запросов, которую можно утащить — Telescope и Pulse на этих сервисах не установлены в принципе, а не выключены в конфиге.
  • Перебор кошельков чужим id невозможен — id проверяется уже после HMAC/сессионной аутентификации, а не через маршрутизацию по модели, так что неавторизованный 404 не может случиться раньше проверки доступа.
  • IP нельзя подделать — nginx сам разворачивает реальный адрес через цепочку Cloudflare и docker-хопов и обнуляет любые входящие X-Forwarded-*; приложение не доверяет прокси вслепую.
  • Доступ в панель именной — учётки заводятся вручную одной командой, общего пароля нет, у каждого кошелька есть created_by.
  • API снаружи не существует — Caddy отвечает 404 на /api/* для публичного домена, nginx повторяет тот же отказ независимо, и приложение проверяет это третий раз само.

Архитектура

Технологии
Backend — Laravel 13, PHP 8.4
Очередь — Redis, отдельный queue:work-процесс на генерацию
Сайдкар — Node 22, @ton/crypto, @ton/ton
Хранилище — MySQL, метаданные кошельков и аудит
Безопасность и эксплуатация
Аутентификация — HMAC-SHA256, nonce, окно времени, идемпотентность
Сеть — приватный API-канал только на 127.0.0.1 / VPN, публичный отдаёт 404
Панель — именной логин, QR-код адреса, экспорт метаданных CSV
Обслуживание — плановая очистка просроченных записей по расписанию

Результат

Внутренние сервисы получают одноразовые тестовые кошельки без риска зацепить боевые ключи, а там, где на кону настоящие деньги, — тот же проверенный механизм на полностью изолированном сервисе: своя сеть, своя база, свои секреты. Сид-фраза существует в открытом виде ровно один ответ одного запроса — до и после него её просто нет.

← Подробнее об услуге на странице услуг

Laravel 13 · PHP 8.4 · Node 22 · Redis · MySQL · TON SDK · HMAC-SHA256


Сделать заказ

| необходим для связи с вами
В кротчайшие сроки я свяжусь с вами.

Также вы можетете связать со мной:
telegram: @ifwcom