NFT в TON: от Tact-контрактов до публикации коллекций

Кейс · TON NFT · Tact · Laravel · 2026

В существующий микросервис GRAM добавлен полный NFT-контур: стандартные смарт-контракты Collection и Item, публичные metadata, чтение состояния из блокчейна, transfer, CLI-команды и медиатека, из которой выбранные изображения можно последовательно выпустить в TON testnet.

12 этаповот контракта до TON-экосистемы
23 NFTвыпущено в testnet
10 × 2две авторские серии опубликованы
14 тестовконтракты Collection и Item

Коротко: работа не ограничилась кнопкой 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.

Коллекция GRAM Genesis и первые NFT в интерфейсе Getgems
Первые NFT GRAM Genesis после deploy и mint в TON testnet.

Медиатека вместо ручного mint

Одна CLI-команда удобна для проверки контракта, но не подходит для работы с серией изображений. Поэтому в административной панели появился раздел «NFT / Медиа».

  1. Создать медиаколлекцию

    Название объединяет материалы в рабочую серию. Это редакционная сущность приложения, а не новый контракт в блокчейне.

    name · UUID · manifest
  2. Загрузить исходники

    За один подход можно загрузить до 50 изображений. Файлы получают безопасные UUID-имена, поэтому одинаковые исходные названия не конфликтуют.

    batch upload · validation
  3. Выбрать финальные работы

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

    selection · bulk delete
  4. Добавить историю

    Описание серии хранится в Markdown и безопасно преобразуется в HTML для предпросмотра. При выпуске текст становится частью snapshot metadata.

    story.md · safe render
  5. Опубликовать в TON

    Система проверяет Collection, подписывающий кошелёк, получателя и баланс, создаёт очередь из Item и последовательно ждёт подтверждение каждого mint.

    queue · balance guard · reconciliation

Так были подготовлены и опубликованы две серии по десять работ: ONE WAY DOLLARBOUND и Greenback Odyssey. Повторное нажатие не создаёт дубли: уже зарезервированные или выпущенные файлы исключаются из следующей публикации.

Почему публикация идёт последовательно

Индекс NFT определяется состоянием Collection непосредственно перед mint. Если отправить десять операций параллельно с одним ожидаемым индексом, часть сообщений станет неоднозначной или потребует сложной сверки. Поэтому очередь обрабатывает Item по одному:

  1. читает актуальный next_item_index;
  2. создаёт URL неизменяемого snapshot metadata;
  3. отправляет mint от owner-кошелька Collection;
  4. ожидает подтверждение транзакции;
  5. повторно читает NFT из сети;
  6. сохраняет адрес, индекс, owner и hash транзакции;
  7. только после этого переходит к следующему изображению.

Если ответ провайдера потерян после отправки, запись не помечается как обычная ошибка. Она переходит в состояние 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 у тестовой коллекции относится к каталожному доверию площадки, а не к доступности изображения или корректности контракта.

Адаптивное отображение коллекции GRAM Genesis в TON-маркетплейсе
Коллекция читается внешним сервисом: название, обложка, Item и атрибуты приходят из публичных metadata.

CLI и эксплуатация

Команды управления

  • nft:deploy — развернуть Collection
  • nft:mint — выпустить Item
  • nft:show — прочитать on-chain состояние
  • nft:transfer — сменить владельца Item
  • nft:sync — восстановить read-модель

Защитные проверки

  • запрет случайной работы не в testnet
  • проверка owner и ожидаемого индекса
  • проверка баланса до серии операций
  • идемпотентность и защита от дублей
  • аудит действий администратора

CLI нужен не вместо админки, а рядом с ней: он даёт воспроизводимый инструмент диагностики, ручной проверки и аварийного восстановления без прямого редактирования базы.

Результат

В GRAM появился законченный NFT-контур — от Tact-контрактов до рабочего редакционного процесса. Контракты протестированы, Collection развёрнута в TON testnet, mint и transfer подтверждены сетью, metadata читаются внешними индексаторами, а локальная read-модель восстанавливается из блокчейна.

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

Открыть NFT Collection в Tonviewer →

Laravel · PHP · Tact · TypeScript · TON testnet · TEP-62 · metadata · Redis Queue · Docker