Как правильно принимать TON-депозиты: вебхуки, опкоды и защита от «пыли»
Приём входящего перевода выглядит как одна операция «пришли деньги — зачислили баланс». На практике это один из самых опасных участков backend-логики в crypto-проекте.
При разработке backend для приёма TON-депозитов на P2P-бирже пришлось закрыть несколько неочевидных сценариев, которые не видны в документации TON, но регулярно всплывают в реальном трафике сети.
Проблема 1: не любое входящее сообщение — это депозит
Кошелёк в TON — это смарт-контракт, и на его адрес может прийти не только «перевод от пользователя», но и служебное сообщение протокола. Например, Jetton-кошелёк отправляет владельцу transfer_notification и excesses — технические уведомления с ненулевым TON-балансом внутри, но не являющиеся депозитом в токене, который нужно зачислить.
Если обрабатывать входящие транзакции по факту прихода TON, такие служебные сообщения периодически «пролезают» как настоящие депозиты. Решение — фильтрация по opcode входящего сообщения: перед тем как поставить транзакцию в очередь на зачисление, сервис проверяет, не относится ли она к списку известных технических опкодов Jetton-протокола, и если да — не создаёт заявку на зачисление вовсе.
Проблема 2: «пыль», которую нельзя зачислить
TON-сеть допускает переводы почти любого размера, включая суммы, которые после вычета комиссий сети физически невозможно превратить в зачисление на внутренний баланс — так называемая dust-транзакция. Если не отсекать такие суммы на уровне бизнес-логики, растёт число «зависших» депозитов: пользователь видит входящую транзакцию в истории кошелька, а зачисления на баланс не происходит, и это выглядит как баг, а не как экономически нецелесообразная операция.
Правильный подход — явный порог creditable-суммы, задаваемый конфигурацией сети, и осознанный отказ создавать outbox-запись для сумм ниже порога, вместо того чтобы пытаться зачислить их и получать ошибку на следующем шаге.
Проблема 3: между «увидели транзакцию» и «отдали деньги на сайт» время идёт
Депозит проходит несколько этапов: TON-нода видит транзакцию → микросервис сохраняет её у себя → задача в очереди доставляет информацию о зачислении на основной сайт биржи. Между вторым и третьим шагом могут пройти секунды или минуты — ретраи очереди, недоступность сети, накопленный backlog джобов.
Если на третьем шаге слепо доверять данным, сохранённым на первом, можно доставить на сайт информацию, которая перестала быть актуальной. Поэтому перед фактической отправкой на сайт джоба выполняет повторную проверку актуального состояния транзакции — а не просто передаёт то, что было закешировано в момент постановки в очередь.
Итог: приём депозита должен быть идемпотентным, консервативным (при сомнении — не зачислять) и проверяющим состояние заново на каждом шаге, а не полагающимся на снимок данных из прошлого. Именно так устроена связка «TON-микросервис ↔ основной сайт»: outbox-таблица с уникальными ограничениями по хэшу транзакции, статус зачисления пересчитывается перед каждой доставкой, а сама доставка защищена от параллельного повторного запуска.
Обсудить задачу
Если у вас есть проект, связанный с Laravel, CRM, Telegram, AI/RAG, API-интеграциями, автоматизацией или TON/GRAM-логикой — напишите в свободной форме, что нужно сделать.
Можно описать задачу коротко: что есть сейчас, что не работает, какой результат нужен и какие сервисы уже используются.
