Мультичейн-платформа: криптоплатежи и кастоди

Приём и выплата криптовалют в сетях EVM, Tron и TON: ключи живут в изолированном подписанте, а каждое движение денег — устойчивая сага.

Область
Финтех, криптокастоди
Наша роль
Архитектура, бэкенд, криптография, фронтенд, DevOps
Год
2023-2026

Мы построили для крупной массовой потребительской платформы бэкенд приёма платежей и хранения криптовалют в сетях EVM, Tron и TON. Он выдаёт адреса для депозитов, находит входящие платежи, собирает средства на горячий кошелёк, проводит выводы и сообщает о событиях бизнес-бэкенду через вебхуки с подписью; депозиты, сборы и выводы — саги на Temporal, доступ — через Keycloak. Система прошла два поколения: в продакшене ключи хранятся в изолированном HD-подписанте за узким интерфейсом подписи, а подписант на пороговой ECDSA (MPC) находится в пилоте.

Задача

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

Решение

Первое поколение (2023–2024) — набор контейнерных сервисов на Python: мультитенантный шлюз с API-ключами и колбэками для каждого клиента, подключаемые адаптеры для EVM, Tron и TON, парсеры переводов ERC-20, TRC-20 и TON-жетонов, очередь колбэков с повторами. Оно решило задачу процессинга, но в эксплуатации стало видно, что кастоди требует более строгих гарантий, и во втором поколении (2026) мы переписали систему с нуля на трёх принципах. Ключи изолированы: API и сканер знают только расширенный публичный ключ, а подписант принимает дайджест и возвращает подпись. Каждое движение денег — сага в Temporal с идемпотентными шагами, поэтому после сбоя на любом шаге процесс безопасно продолжается. Источник истины по балансам — сам блокчейн: система хранит только факты и статусы транзакций, без изменяемых колонок баланса. Итог — четыре компонента: кастоди-ядро, REST API под защитой Keycloak, консоль оператора на React и подписант.

Архитектура

Архитектура: кастоди-ядро (API, сканер блоков, воркер саг), PostgreSQL и Temporal, изолированная зона подписи и адаптеры сетей EVM, Tron и TON.
Архитектура: кастоди-ядро (API, сканер блоков, воркер саг), PostgreSQL и Temporal, изолированная зона подписи и адаптеры сетей EVM, Tron и TON.

Что умеет система

Адреса из публичного ключа
Адреса депозитов выводятся из расширенного публичного ключа по BIP-32/44 и привязываются к пользователям через внешние идентификаторы.
Автоматический сбор средств
Средства уходят на горячий кошелёк по одной из трёх стратегий оплаты комиссий: платить за каждый сбор, делегировать застейканные ресурсы сети или арендовать ресурсы на внешней площадке с автоматическим откатом.
Выводы с лимитами
Ключи идемпотентности, лимиты на транзакцию и скользящие суточные лимиты, проверка ликвидности горячего кошелька с учётом транзакций в пути.
Интеграция через вебхуки
События deposit.detected, sweep.done, withdrawal.confirmed и withdrawal.failed подписываются HMAC и доставляются с повторами; для отладки есть тестовая отправка.
Консоль оператора
Адреса с живыми балансами, депозиты, сборы, выводы, кошельки, настройки активов и колбэков, состояние подписанта. Роли viewer, operator и admin через Keycloak.
Настройки без перезапуска
RPC-провайдеры, активы, лимиты и колбэки хранятся в базе и применяются за секунды; переключение между тестовой и основной сетью — в один клик.

Инженерные решения

  • Подписант заблокирован по умолчанию: после старта администратор вводит seed через консоль, система сверяет его с сохранённым публичным ключом и держит только в памяти — секреты не попадают ни в файлы, ни в переменные окружения, ни в базу, ни в историю workflow.
  • Подписант на пороговой ECDSA (в пилоте) написан на Rust на протоколах семейства CGGMP: подпись идёт при ответе кворума, узел-нарушитель вычисляется и исключается, доли ключа периодически обновляются без смены публичного ключа и адресов. Переход на него — смена конфигурации, а не кода.
  • Защита от двойной отправки: каждая транзакция сохраняет свой txid до трансляции, а при повторе система ищет квитанцию вместо новой отправки.
  • Сканер читает только финализированные блоки и записывает депозиты вместе с курсором в одной транзакции базы, а саги запускаются с детерминированным id — повторно прочитанный блок не создаст дубль.
  • Стратегию оплаты комиссий выбрали по замерам в тестовой сети: предсказуемая плата за каждый сбор вместо замороженного в стейкинге капитала, остальные стратегии — за переключателем для больших объёмов.

Интерфейс

Схемы

Путь денег: выдача адреса, обнаружение депозита, сбор средств на горячий кошелёк и вывод — каждый шаг как сага в Temporal.
Путь денег: выдача адреса, обнаружение депозита, сбор средств на горячий кошелёк и вывод — каждый шаг как сага в Temporal.
Два поколения: мультитенантный процессинг (2023–2024) и кастоди на HD-кошельках с отдельным подписантом (2026).
Два поколения: мультитенантный процессинг (2023–2024) и кастоди на HD-кошельках с отдельным подписантом (2026).

Стек

  • Python 3.12
  • FastAPI
  • SQLAlchemy 2 (async)
  • PostgreSQL
  • Temporal
  • web3.py
  • tronpy
  • TON SDKs
  • Rust
  • tonic gRPC + mTLS
  • CGGMP threshold ECDSA
  • Keycloak
  • React 19 + TypeScript
  • Docker

Что это показывает

  • Проектируем мультичейн-кастоди (EVM, Tron, TON), где ключи изолированы за узким и проверяемым интерфейсом подписи.
  • Применяем прикладную криптографию: HD-деривацию и пороговую ECDSA с обновлением долей и шифрованием их при хранении.
  • Строим движение денег с гарантией «ровно один раз» на сагах и идемпотентных побочных эффектах.
  • Развиваем системы в продакшене: переносим уроки первого поколения во второе, не таща за собой лишнюю сложность.

Вопросы по кейсу

Где хранятся приватные ключи?

В изолированной зоне подписи. В продакшене это HD-подписант: он стартует заблокированным, seed вводит администратор, и хранится он только в памяти. API и сканер работают с публичным ключом, а за границу зоны выходят только дайджесты и подписи. Подписант на пороговой ECDSA, где каждый узел держит лишь свою долю ключа, сейчас в пилоте.

Что будет, если процесс упадёт посреди вывода?

Вывод — это сага в Temporal, она продолжится с того же шага. Txid сохраняется до трансляции, поэтому при повторе система проверяет квитанцию в сети, а не отправляет транзакцию заново.

Как платформа интегрируется с нашим бэкендом?

Запросы идут через REST API с сервисным аккаунтом Keycloak (client credentials), события приходят вебхуками с подписью HMAC и повторами до успешного ответа. Выводы принимают ключ идемпотентности, так что повторный запрос не создаст второй вывод.