Розробка метаданих NFT (on-chain/off-chain)
tokenURI() повертає рядок — URL або base64-encoded JSON. За цією простотою скривається архітектурне рішення, яке визначить долю колекції на роки вперед. Метаданні на централізованому IPFS gateway — це не «децентралізовані метаданні», це посилання на сервер Pinata, який може зникнути. NFT з метаданнями на контракті (on-chain) переживе будь-яке hosting.
On-chain проти off-chain: реальні компроміси
Повністю on-chain
Метаданні зберігаються прямо в смарт-контракті. tokenURI() генерує JSON та SVG в runtime через string concatenation:
function tokenURI(uint256 tokenId) public view override returns (string memory) {
string memory json = Base64.encode(bytes(string(abi.encodePacked(
'{"name":"Token #', Strings.toString(tokenId),
'","description":"On-chain NFT","image":"data:image/svg+xml;base64,',
Base64.encode(bytes(_generateSVG(tokenId))),
'"}'
))));
return string(abi.encodePacked("data:application/json;base64,", json));
}
Перевага: повна постійність, немає залежності від зовнішніх сервісів. Недостаток: газ на деплой зростає з розміром SVG. Для простої generative коллекції (Loot, Nouns-стиль) це працює. Для фотографій — ні.
Зберігання атрибутів у storage: маппінг tokenId → struct з trait values. Кожен атрибут — uint8 або bytes32 для економії слотів. uint8 атрибути пакуються по 32 у один storage slot.
IPFS off-chain
Стандартний підхід для більшості коллекцій. Метаданні загружаються в IPFS, tokenURI() повертає ipfs://CID/tokenId.json. Критична вимога: не використовуйте HTTP gateway в URI.
Правильно: ipfs://QmHash/1.json
Неправильно: https://ipfs.io/ipfs/QmHash/1.json
Друга варіант — посилання на конкретний HTTP сервер. Він може зникнути. Перша — контентна адреса, яка працює з будь-яким IPFS gateway.
Для пиннінгу — Pinata + Web3.Storage як backup. Для найважливіших коллекцій — Filecoin через NFT.Storage для довгострокового зберігання з криптографічною гарантією.
Механізм reveal
Pre-reveal: всі токени показують placeholder метаданні. Post-reveal: реальні метаданні розкриваються. Наївна реалізація — owner просто змінює baseURI. Це централізовано й потребує довіри.
Схема commit-reveal на VRF: перед mintом owner коммітить хеш seed, після завершення mint — публікує seed та викликає Chainlink VRF для отримання випадкового offset. Метаданні перемішуються детерміновано через (tokenId + offset) % totalSupply. Ніхто не може заздалегідь знати, які traits доведуть конкретному токену.
function fulfillRandomWords(uint256, uint256[] memory randomWords) internal override {
revealOffset = randomWords[0] % maxSupply;
revealed = true;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(revealed, "Not revealed yet");
uint256 metadataId = (tokenId + revealOffset) % maxSupply;
return string(abi.encodePacked(baseURI, metadataId.toString(), ".json"));
}
Структура JSON метаданих
OpenSea ERC-721 metadata стандарт:
{
"name": "Token #1",
"description": "Текст опису",
"image": "ipfs://CID/1.png",
"external_url": "https://project.xyz/token/1",
"attributes": [
{"trait_type": "Background", "value": "Blue"},
{"trait_type": "Rarity", "value": "Legendary", "display_type": "boost_percentage", "max_value": 100}
]
}
display_type керує відображенням в OpenSea. Числові атрибути: "number" (просто число), "boost_percentage" (прогрес-бар), "boost_number" (модифікатор), "date" (unix timestamp → дата).
Для ERC-1155 структура аналогічна, але tokenURI приймає uint256 id та може використовувати {id} placeholder в URI.
Інструменти й процес
Генерація метаданих для великої коллекції — off-chain TypeScript скрипт: загружає layers, генерує комбінації з урахуванням рарити weights, створює JSON файли та зображення, batch загружає в IPFS через Pinata API. Потім IPFS CID фіксується в контракті.
Для on-chain SVG — TypeScript скрипт генерує Solidity бібліотеки зі string констант для кожного trait. Розмір контракту перевіряється: ліміт 24KB.
Ориєнтири по термінах
IPFS off-chain метаданні з reveal механізмом — 2-3 дні. On-chain SVG генерація для generative коллекції — 3-5 днів залежно від складності арту.







