GRAM: создание и привязка TON-кошельков
TON · Laravel · wallet microservice · testnet / mainnet
GRAM — отдельный микросервис, который создаёт и привязывает TON-кошельки, синхронизирует транзакции и передаёт подтверждённые депозиты в основное приложение. В этом разборе показана фактическая архитектура проекта: от V5R1-кошелька до защищённого межсервисного API.
Зачем кошельки вынесены в отдельный сервис
Основной сайт хранит пользователей и бизнес-операции. GRAM отвечает за работу с TON: адреса, ключевой материал, балансы, историю транзакций, депозиты, вывод и сбор средств. Такое разделение сокращает область кода, которая имеет доступ к чувствительным данным, и позволяет независимо масштабировать фоновые задачи.
Связь между системами проходит через REST API. Основной сайт запрашивает кошелёк по своему идентификатору пользователя, а GRAM возвращает публичные данные: адрес, сеть, версию, назначение, статус и балансы. Зашифрованная мнемоника в этот ответ не попадает.
Как создаётся и привязывается пользовательский кошелёк
-
Запрос основного приложения
Основной сайт передаёт внутренний ID пользователя и активную сеть. Запрос проходит HMAC-проверку до выполнения бизнес-операции.
-
Проверка существующей привязки
GRAM ищет кошелёк этого пользователя в той же сети и версии V5R1. Если запись уже существует, сервис возвращает её и не создаёт второй адрес.
-
Генерация и сохранение
TON-адаптер создаёт V5R1-кошелёк. В базе фиксируются адрес, публичный ключ, workchain, global ID, источник ключа и назначение. Мнемоника сохраняется в зашифрованном виде.
-
Фоновая синхронизация
Для нового адреса ставится задача подписки на webhook. Баланс и история обрабатываются очередью, поэтому сетевой TON API не задерживает пользовательский запрос.
Конкурентные запросы также учтены: уникальное ограничение в базе не позволяет создать несколько кошельков для одной и той же привязки. При конфликте сервис повторно читает уже созданную запись.
Три роли кошельков
Пользовательский
Связан с ID пользователя основного сайта. Принимает депозиты и участвует в автоматическом сборе средств по заданному порогу.
purpose: userКошелёк-сборщик
Используется как целевой адрес для консолидации средств с пользовательских кошельков и для операций казначейства.
purpose: collectorАвтономный
Создаётся без связи с пользователем. Подходит для административных переводов и отдельных служебных сценариев.
purpose: standaloneСоздание и безопасный импорт
GRAM умеет не только генерировать новые кошельки, но и подключать существующие V5R1-кошельки. Перед одиночным импортом оператор указывает ожидаемый адрес: сервис восстанавливает кандидата из 24 слов и проверяет совпадение. Это защищает от импорта корректной seed-фразы с ошибочно выбранными параметрами сети или wallet global ID.
Для пакетной загрузки каждая строка проходит отдельную проверку. Предпросмотр показывает готовые записи, дубли и ошибки, не раскрывая мнемоники в журнале. При сохранении адрес проверяется под блокировкой транзакции, а уникальный индекс закрывает гонку между параллельными импортами.
Операция доступна только администратору с нужной ролью и настроенной 2FA. Seed-фраза исключена из повторного заполнения формы, скрыта в модели и хранится через шифрование Laravel. Просмотр выполняется отдельной защищённой операцией с одноразовым результатом и журналированием.
Webhook, polling и история без дублей
Новая транзакция приходит через webhook и запускает адресную задачу синхронизации. Polling работает как резервный контур: он обновляет баланс и забирает историю по расписанию, даже если внешнее событие задержалось или не дошло.
Задачи синхронизации уникальны для кошелька. При ограничениях со стороны TON-провайдера включается общий cooldown, а повторные попытки получают возрастающую задержку и случайный jitter. Так очередь не создаёт лавину одинаковых запросов.
История читается курсорами. Отдельно хранятся последнее увиденное событие и курсор backfill для загрузки старых операций. Входящая транзакция принимается только при успешном результате без признаков aborted или bounced. Идентификатор операции строится из blockchain logical time, а вставка выполняется через уникальность и insertOrIgnore.
От подтверждённого депозита до баланса основного сайта
Подтверждённая входящая транзакция пользовательского кошелька создаёт запись в outbox. Технические Jetton-сообщения, внутренние переводы между управляемыми кошельками и суммы ниже минимального порога не отправляются как пользовательский TON-депозит.
Для события вычисляется стабильный external_id из сети, адреса, logical time и хеша транзакции. Доставка в основное приложение идёт отдельной очередью с повторными попытками. Получатель обязан вернуть тот же идентификатор в подтверждении; после исчерпания попыток операция уходит на ручную проверку. Это позволяет повторить доставку и не начислить депозит дважды.
Защита межсервисного API
Каждый запрос основного сайта подписывается HMAC-SHA256. В подпись входят HTTP-метод, путь, query string, SHA-256 тела, timestamp, nonce и Idempotency-Key. Сервис проверяет подпись через hash_equals, ограничивает допустимое время запроса и резервирует nonce в базе до выполнения операции.
Replay protection
Одноразовый nonce с уникальным ограничением не позволяет повторно воспроизвести уже принятый запрос.
Idempotency-Key
Повторная команда связывается с тем же бизнес-запросом и не создаёт новую финансовую операцию.
IP allowlist
API-клиенту задаётся точный список IPv4, IPv6 или CIDR. Запрос с другого адреса отклоняется до бизнес-логики.
Ограничения
HTTPS, лимит размера тела, rate limiting и метаданные запроса дополняют подпись. Секреты и содержимое ключей в интерфейсе не выводятся.
Операционная панель и контроль состояния
Администратор видит назначение и источник каждого кошелька, баланс в TON и nanoTON, резерв, время последней синхронизации и ошибку провайдера. Список фильтруется по сети, версии, назначению, статусу, источнику ключа и состоянию синхронизации.
Выбранные кошельки можно отправить на синхронизацию или выгрузить в CSV. Массовые задачи имеют лимит, распределяются по времени с учётом пропускной способности TON API и фиксируются в журнале действий. Для сверки custody строится снимок пользовательских, collector- и standalone-кошельков с общим, зарезервированным и доступным балансом.
Стек проекта
Связь с P2P-платформой
GRAM — кошельковый и blockchain-контур более крупной FinTech-системы. Пользователь получает отдельный TON-адрес, подтверждённый депозит передаётся в основное приложение, а баланс используется в операциях P2P и выводе. Jetton-депозиты и выводы проходят по тому же принципу, но ведутся в отдельных таблицах и собственном журнале.
В полном кейсе P2P-платформы разобраны сделки, чат, споры, ledger, резервирование вывода и custody reconciliation. Код кошелькового микросервиса остаётся отдельной частью архитектуры.
Обсудить задачу
Если у вас есть проект, связанный с Python, Laravel, Node.js, CRM, Telegram, AI/RAG, API-интеграциями, автоматизацией или TON/GRAM-логикой — напишите в свободной форме, что нужно сделать.
Можно описать задачу коротко: что есть сейчас, что не работает, какой результат нужен и какие сервисы уже используются.
