NFT в TON: от Tact-контрактов до публикации коллекций
Кейс · TON NFT · Tact · Laravel · 2026
В существующий микросервис GRAM добавлен полный NFT-контур: стандартные смарт-контракты Collection и Item, публичные metadata, чтение состояния из блокчейна, transfer, CLI-команды и медиатека, из которой выбранные изображения можно последовательно выпустить в TON testnet.
Коротко: работа не ограничилась кнопкой mint. Получился управляемый процесс: загрузить изображения, собрать серию, добавить историю в Markdown, выбрать финальные работы, проверить баланс, поставить выпуск в очередь и подтвердить результат повторным чтением TON.
От кошелькового сервиса к NFT
GRAM уже работал как отдельный Laravel-микросервис для TON: создавал и импортировал кошельки, синхронизировал транзакции, принимал депозиты и взаимодействовал с основным приложением через защищённый API. Эта инфраструктура используется в P2P-платформе на Laravel с TON и Jetton.
Следующий шаг — добавить NFT так, чтобы не дублировать готовые компоненты кошельков, очередей, аудита и работы с сетью. Новый модуль должен был жить внутри той же эксплуатационной модели: testnet перед mainnet, явные роли кошельков, проверка баланса до отправки, фиксация транзакций и восстановление состояния из блокчейна.
Базовый кошельковый контур подробно разобран в услуге создания и привязки TON-кошельков в GRAM. NFT-модуль продолжает эту архитектуру: кошелёк подписывает операцию, но источником истины для выпущенного токена остаётся TON.
Что требовалось реализовать
Blockchain-слой
- NFT Collection с владельцем и последовательной нумерацией
- NFT Item со стандартным transfer
- совместимые get-методы Collection и Item
- запрет mint и transfer посторонним кошельком
Прикладной слой
- публичные JSON metadata и изображения
- команды deploy, mint, show, transfer и sync
- read-модель коллекций, NFT и транзакций в базе
- админка для подготовки и публикации серий
Каждый этап заканчивался проверяемым результатом: контракт компилируется, тест проходит, адрес читается из сети, владелец меняется после transfer, а локальная база может быть заново собрана из on-chain данных.
Collection и Item на Tact
Контракты вынесены в отдельный NFT-модуль и собираются независимо от остального приложения. Collection хранит owner, content и next_item_index, рассчитывает адрес Item по индексу и разрешает mint только владельцу. Нумерация идёт последовательно — без возможности случайно переписать уже существующий NFT.
Item хранит индекс, адрес коллекции, владельца и ссылку на metadata. Transfer реализован по стандартной схеме TON: проверяется отправитель, меняется owner, новому владельцу отправляется ownership_assigned, а остаток сообщения возвращается через excesses.
Проверки Collection
- deploy и начальное состояние
- первый и последовательный mint
- расчёт адреса Item
- рост
next_item_index - отказ постороннему отправителю
Проверки Item
- инициализация владельца и metadata
- успешный transfer
- смена owner после подтверждения
- отказ постороннему отправителю
- обработка некорректных сообщений
Автоматический sandbox-набор содержит 14 контрактных тестов. Они выполняются до testnet-деплоя и проверяют не только успешный сценарий, но и запреты — для blockchain-кода это не менее важно.
Metadata, которые читаются без приложения
Collection и каждый Item получили публичные JSON-документы с названием, описанием, изображением и атрибутами. Файлы доступны по HTTPS без авторизации, отдаются с CORS и не зависят от административной сессии.
Это принципиальное разделение: backend управляет выпуском, но Tonviewer, маркетплейс или другой индексатор должен получить metadata самостоятельно. Проверка выполнялась не только запросом к сайту — внешний TON API успешно загрузил JSON, построил несколько размеров preview и связал Item с Collection.
Медиатека вместо ручного mint
Одна CLI-команда удобна для проверки контракта, но не подходит для работы с серией изображений. Поэтому в административной панели появился раздел «NFT / Медиа».
-
Создать медиаколлекцию
Название объединяет материалы в рабочую серию. Это редакционная сущность приложения, а не новый контракт в блокчейне.
name · UUID · manifest -
Загрузить исходники
За один подход можно загрузить до 50 изображений. Файлы получают безопасные UUID-имена, поэтому одинаковые исходные названия не конфликтуют.
batch upload · validation -
Выбрать финальные работы
Из большой подборки сохраняются до десяти изображений. Ненужные файлы можно быстро удалить — по одному, выбранной группой или все, кроме финального набора.
selection · bulk delete -
Добавить историю
Описание серии хранится в Markdown и безопасно преобразуется в HTML для предпросмотра. При выпуске текст становится частью snapshot metadata.
story.md · safe render -
Опубликовать в TON
Система проверяет Collection, подписывающий кошелёк, получателя и баланс, создаёт очередь из Item и последовательно ждёт подтверждение каждого mint.
queue · balance guard · reconciliation
Так были подготовлены и опубликованы две серии по десять работ: ONE WAY DOLLARBOUND и Greenback Odyssey. Повторное нажатие не создаёт дубли: уже зарезервированные или выпущенные файлы исключаются из следующей публикации.
Почему публикация идёт последовательно
Индекс NFT определяется состоянием Collection непосредственно перед mint. Если отправить десять операций параллельно с одним ожидаемым индексом, часть сообщений станет неоднозначной или потребует сложной сверки. Поэтому очередь обрабатывает Item по одному:
- читает актуальный
next_item_index; - создаёт URL неизменяемого snapshot metadata;
- отправляет mint от owner-кошелька Collection;
- ожидает подтверждение транзакции;
- повторно читает NFT из сети;
- сохраняет адрес, индекс, owner и hash транзакции;
- только после этого переходит к следующему изображению.
Если ответ провайдера потерян после отправки, запись не помечается как обычная ошибка. Она переходит в состояние reconciliation: сначала нужно проверить блокчейн и лишь затем решать, допустим ли повтор.
Два кошелька — две разные роли
В testnet-контуре роли разделены. Кошелёк, указанный owner при deploy Collection, остаётся техническим подписантом mint-транзакций и оплачивает gas. Получателем новых NFT может быть другой кошелёк — именно он становится owner каждого Item.
Передача уже выпущенных NFT на другой адрес не меняет owner самой Collection. Если для новой серии нужен другой владелец контракта, разворачивается отдельная Collection с правильным owner с самого начала.
Эта граница отражена и в интерфейсе: перед публикацией проверяется баланс подписанта, а в создаваемой записи отдельно фиксируется кошелёк-получатель. Пользователь видит понятную ошибку до постановки задания в очередь, если TON для всей серии недостаточно.
Чтение из TON и восстановление локального состояния
Таблицы nft_collections, nft_items и nft_transactions ускоряют интерфейс, но не объявлены источником истины. Сервис умеет получить Collection, Item или NFT по индексу через get-методы контракта.
Команда синхронизации проверена на пустой локальной NFT-базе: она прочитала Collection и выпущенные Item непосредственно из TON и восстановила read-модель. Это защищает систему от ситуации, когда локальная запись потеряна, а blockchain-транзакция уже необратимо состоялась.
Transfer и проверка совместимости
После первых mint один из NFT был передан между двумя собственными testnet-кошельками. До операции owner читался с исходного адреса, после подтверждения get_nft_data вернул нового владельца, а hash transfer сохранился в журнале транзакций.
Коллекция и Item проверены в Tonviewer, Tonscan, TonAPI и интерфейсе Getgems. Индексаторы загрузили metadata и изображения, распознали контракт и построили preview. Отметка UNVERIFIED у тестовой коллекции относится к каталожному доверию площадки, а не к доступности изображения или корректности контракта.
CLI и эксплуатация
Команды управления
nft:deploy— развернуть Collectionnft:mint— выпустить Itemnft:show— прочитать on-chain состояниеnft:transfer— сменить владельца Itemnft:sync— восстановить read-модель
Защитные проверки
- запрет случайной работы не в testnet
- проверка owner и ожидаемого индекса
- проверка баланса до серии операций
- идемпотентность и защита от дублей
- аудит действий администратора
CLI нужен не вместо админки, а рядом с ней: он даёт воспроизводимый инструмент диагностики, ручной проверки и аварийного восстановления без прямого редактирования базы.
Результат
В GRAM появился законченный NFT-контур — от Tact-контрактов до рабочего редакционного процесса. Контракты протестированы, Collection развёрнута в TON testnet, mint и transfer подтверждены сетью, metadata читаются внешними индексаторами, а локальная read-модель восстанавливается из блокчейна.
Главный практический результат — выпуск NFT перестал быть набором ручных команд. Автор может загрузить большую подборку, выбрать десять работ, приложить историю и запустить контролируемую публикацию, где у каждого изображения есть понятный статус, индекс, адрес и транзакция.
Laravel · PHP · Tact · TypeScript · TON testnet · TEP-62 · metadata · Redis Queue · Docker
