Как создать NFT-коллекцию на TON и заминтить NFT с Lottie-анимацией — полный гайд
Привет, Пикабу! Сегодня расскажу как я без знания блокчейна с нуля задеплоил собственную NFT-коллекцию на TON и заминтил NFT с живой анимацией. По пути нашёл несколько граблей — покажу как их обойти.


Что получилось в итоге
Собственная NFT-коллекция на TON мейннете
NFT с картинкой + Lottie-анимацией (воспроизводится прямо на странице GetGems)
Скрипт для минта новых NFT в одну команду
Стоимость: ~0.3 TON на деплой коллекции + ~0.1 TON за каждый последующий NFT
Что понадобится
Node.js (v18+)
Кошелёк Tonkeeper с минимум 0.5 TON на балансе
Аккаунт на Pinata (бесплатный тариф — хватит) — для хранения файлов в IPFS
API ключ TonCenter — получить бесплатно через @tonapibot в Telegram
Шаг 1. Готовим файлы
Для каждого NFT нужно два файла:
Картинка — обложка NFT (PNG/JPG, лучше квадратная 1:1)
Lottie JSON — анимация (если нужна). Lottie — это векторная анимация в формате JSON, которую GetGems умеет воспроизводить прямо на странице NFT
Если анимации нет — можно только с картинкой, поле lottie просто не указываем.
Шаг 2. Загружаем файлы на IPFS через Pinata
Идём на app.pinata.cloud → загружаем оба файла → получаем CID для каждого. CID выглядит примерно так:
QmfBzxTrLpXrceeEvYWc958Z2NuMGQLPNtSaq8hYr13QtZ
Это и есть постоянный адрес файла в IPFS. Файл будет доступен по URL:
Шаг 3. Устанавливаем зависимости
mkdir nft-mint && cd nft-mint
npm init -y
npm install @ton/ton @ton/crypto @Pinata/sdk dotenv
Создаём .env:
MNEMONIC=слово1 слово2 ... слово24
PINATA_API_KEY=ваш_ключ
PINATA_API_SECRET=ваш_секрет
TONCENTER_API_KEY=ваш_ключ
⚠️ Добавьте .env в .gitignore — никогда не коммитьте сид-фразу!
Шаг 4. Важный момент — версия кошелька
Tonkeeper сейчас по умолчанию создаёт WalletV5R1, а большинство старых туториалов используют WalletV4. Если использовать не ту версию — скрипт будет работать с другим адресом и транзакции не пройдут.
Чтобы проверить версию своего кошелька, запускаем скрипт который перебирает версии:
const versions = [
WalletContractV1R1, WalletContractV1R2, WalletContractV1R3,
WalletContractV2R1, WalletContractV2R2,
WalletContractV3R1, WalletContractV3R2,
WalletContractV4,
WalletContractV5R1,
];
Та версия, адрес которой совпадёт с вашим адресом в Tonkeeper — и есть ваша версия.
Шаг 5. Почему НЕ стоит минтить в чужую коллекцию
Первый мой NFT я попробовал заминтить в уже существующую коллекцию GetGems Launchpad. NFT создался, но метаданные не отобразились — показывалась заглушка "Unavailable".
Причина: у той коллекции commonContentUrl указывал на CDN GetGems (https://s.getgems.io/nft/c/...). Их контракт ожидал в individual_content только суффикс типа "0/meta.json", а я положил полный IPFS URL — в итоге получился сломанный адрес вида https://s.getgems.io/nft/c/.../ipfs://Qm....
Решение: деплоить собственную коллекцию с пустым commonContentUrl = "". Тогда individual_content — это полный IPFS URL, и всё работает корректно.
Шаг 6. Деплоим коллекцию
Метаданные коллекции (collection.json) по стандарту TEP-64:
{
"name": "Моя коллекция",
"description": "Описание",
"image": "ipfs://Qm...",
"social_links": [
]
}
Загружаем collection.json на Pinata, получаем CID.
Ключевой момент при построении stateInit коллекции — commonContentUrl должен быть пустой ячейкой:
function buildCollectionStateInit(collectionMetaUrl, ownerAddress) {
const commonContent = beginCell().endCell(); // ← пустая!
// ...
}
Ещё один сюрприз: BOC-строки стандартных контрактов из документации часто скопированы с артефактами и дают ошибку Invalid CRC32C. Самый надёжный способ — вытащить BOC прямо из блокчейна с уже задеплоенной коллекции:
const state = await client.getContractState(Address.parse('адрес_рабочей_коллекции'));
const boc = state.code.toBoc().toString('base64');
Шаг 7. Минтим NFT
Метаданные NFT (nft.json):
{
"name": "SportBull #1",
"description": "Описание NFT",
"image": "ipfs://Qm...",
"lottie": "ipfs://Qm...",
"attributes": [
{ "trait_type": "Type", "value": "Animated" }
]
}
Тело транзакции минта (op=1, стандарт TEP-62):
function createMintBody({ itemIndex, itemOwnerAddress, metadataUrl, amount }) {
const body = beginCell();
body.storeUint(1, 32); // op: deploy_nft_item
body.storeUint(0, 64); // query_id
body.storeUint(itemIndex, 64); // номер NFT
body.storeCoins(amount); // TON для деплоя item-контракта
const nftItemContent = beginCell();
nftItemContent.storeAddress(itemOwnerAddress);
const uriContent = beginCell();
uriContent.storeBuffer(Buffer.from(metadataUrl)); // полный ipfs:// URL
nftItemContent.storeRef(uriContent.endCell());
body.storeRef(nftItemContent.endCell());
return body.endCell();
}
Запускаем:
node mint.js
Скрипт сам:
Загрузит метаданные на Pinata
Прочитает nextItemIndex из коллекции
Отправит транзакцию
Подождёт подтверждения
Выведет адрес NFT и ссылку на GetGems
Результат
✅ Метаданные загружены: ipfs://QmSRD6...
👛 Кошелёк: UQBPSO7...
Баланс: 2.239 TON
📋 next_item_index: 1
🚀 Минтим NFT item #1...
✅ Подтверждено
🎉 NFT: https://getgems.io/nft/EQA90T8s...
Исходники
Готовый скрипт с комментариями: github.com/freesbots/mint_nft_TON
В репозитории:
deploy-collection.js — деплой коллекции + минт первого NFT
mint.js — минт в существующую коллекцию
check-collection.js — проверка состояния коллекции
.env.example — шаблон переменных окружения







