NFT на фронте: ERC-721 и метаданные

ERC-721 — стандарт NFT. Как получить токены пользователя, прочитать tokenURI и показать картинку и свойства.

ERC-721 — стандарт невзаимозаменяемых токенов (NFT): каждый токен уникален, имеет свой tokenId и ссылку на метаданные (tokenURI) с картинкой и атрибутами.

NFT на фронте — это «галерея»: показать, чем владеет пользователь, с картинками и свойствами. Особенность в том, что сама картинка и описание лежат не в блокчейне, а по ссылке из метаданных. Фронт должен пройти цепочку: контракт → tokenURI → JSON-метаданные → image.

Цепочка получения NFT

tokenId  --(tokenURI)-->  ссылка (часто ipfs://...)
   |                          |
   |                     fetch JSON
   v                          v
владелец                { name, description, image: "ipfs://..." }
                               |
                          показать картинку

Чтение метаданных

const nft = new Contract(addr, erc721Abi, provider);

// сколько NFT у пользователя
const count = await nft.balanceOf(user); // bigint

// получить tokenId по индексу (если контракт поддерживает enumerable)
const tokenId = await nft.tokenOfOwnerByIndex(user, 0);

// ссылка на метаданные
const uri = await nft.tokenURI(tokenId); // напр. "ipfs://CID/1.json"

// метаданные — обычный JSON по этой ссылке (через IPFS-шлюз)
const meta = await fetch(toHttp(uri)).then(r => r.json());
// meta.image тоже часто ipfs:// — его надо превратить в https через шлюз

Структура метаданных (стандарт)

{
  "name": "Cool Cat #1",
  "description": "Описание токена",
  "image": "ipfs://bafy.../1.png",
  "attributes": [
    { "trait_type": "Background", "value": "Blue" },
    { "trait_type": "Eyes", "value": "Laser" }
  ]
}

Поля name, image, attributes — негласный стандарт (OpenSea metadata). По attributes фронт рисует «свойства» NFT, по image — картинку.

ipfs:// нельзя показать напрямую

Браузер не умеет ipfs://. Ссылку превращают в HTTPS через IPFS-шлюз:

function toHttp(uri) {
  if (uri.startsWith("ipfs://")) {
    const cid = uri.slice("ipfs://".length);
    return "https://ipfs.io/ipfs/" + cid;
  }
  return uri;
}

console.log(toHttp("ipfs://bafyABC/1.png"));
console.log(toHttp("https://example.com/img.png"));

Вывод:

https://ipfs.io/ipfs/bafyABC/1.png
https://example.com/img.png

Как работает под капотом

В блокчейне у NFT хранится минимум: кто владелец какого tokenId и базовый URI. Картинки и атрибуты дороги для хранения on-chain, поэтому их кладут в IPFS, а контракт хранит лишь ссылку. Фронт делает обычный fetch метаданных и картинки через шлюз. Поэтому «отображение NFT» — это на 80% обычная веб-работа (fetch + img) и на 20% чтение контракта. Заметьте: метаданные могут быть на централизованном сервере — тогда NFT «не полностью децентрализован», и картинка может исчезнуть.

Частые ошибки

  • Пытаться показать ipfs:// в img src. Браузер не поймёт; нужен шлюз.
  • Ждать картинку «в контракте». On-chain только ссылка; данные — по tokenURI.
  • Считать, что у всех NFT есть enumerable. tokenOfOwnerByIndex не у всех контрактов; иногда токены ищут через события Transfer или индексатор.

Итоги

  • ERC-721 — уникальные токены; данные о виде лежат в метаданных по tokenURI.
  • Цепочка: contract → tokenURI → JSON → image; ipfs:// превращают в https через шлюз.
  • Отображение NFT — в основном обычный fetch плюс пара вызовов контракта.
Проверьте себя
1. Где у NFT хранятся картинка и атрибуты?
AПрямо в блокчейне в контракте
BПо ссылке tokenURI (часто в IPFS), а on-chain только ссылка
CВ кошельке пользователя
DВ localStorage фронта
2. Почему ipfs://... нельзя поставить прямо в img src?
AIPFS платный
BБраузер не понимает протокол ipfs://, нужен HTTPS-шлюз
CЭто нарушает ERC-721
Dipfs:// только для видео
3. Что описывает поле attributes в метаданных NFT?
AАдрес владельца
BСвойства/трейты токена для отображения
CПриватный ключ
DchainId сети