ERC-721: метаданные и содержание абонемента
ownerOf отвечает на вопрос о текущем владельце номера. Для отображения абонемента нужны название, изображение и описание его учебного назначения. Эти сведения обычно размещаются вне состояния ERC-721, а tokenURI возвращает адрес описания. Сам по себе URI не гарантирует ни вечную доступность, ни неизменность содержимого.
Добавим к идее LibraryPass отдельную коллекцию FixedMetadataPass. Отличие сформулируем прямо: каждый новый номер получает одну непустую строку URI, и показанный интерфейс не позволяет менять её впоследствии. Передачи токена сохраняют эту строку. Это иной экземпляр с собственным адресом, а не скрытое обновление старого контракта.
Запись до безопасного выпуска
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract FixedMetadataPass is ERC721, Ownable {
error EmptyURI();
uint256 public nextId = 1;
mapping(uint256 => string) private descriptions;
constructor(address admin)
ERC721("FixedMetadataPass", "FMP") Ownable(admin) {}
function issue(address to, string calldata uri)
external onlyOwner returns (uint256 id)
{
if (bytes(uri).length == 0) revert EmptyURI();
id = nextId++;
descriptions[id] = uri;
_safeMint(to, id);
}
function tokenURI(uint256 id) public view override returns (string memory) {
_requireOwned(id);
return descriptions[id];
}
}
Листинг полный и учебный. У этой модели нет прежнего лимита сто выдач: ограничения старого примера нельзя переносить по сходному названию. Если ограничение необходимо продукту, его добавляют явно, вместе с проверкой и объяснением. Сейчас предмет урока — момент записи описания и граница его неизменности.
descriptions заполняется до _safeMint. Получатель с callback может прочитать tokenURI уже существующего токена и получить подготовленную строку. Если приём отклонится, запись URI и увеличение nextId также откатятся. Не получится успешного описания для несостоявшегося выпуска.
Как выглядит описание
Для локального учебного файла можно использовать такую форму JSON. Она демонстрирует обычные поля отображения, а не устанавливает самостоятельный стандарт доступа ко всем сервисам.
{
"name": "Учебный абонемент №1",
"description": "Пример для курса ProfessorWeb",
"image": "ipfs://example-image-cid/pass.svg",
"attributes": [{"trait_type": "Направление", "value": "Web3"}]
}
example-image-cid является явным текстовым обозначением места для настоящего CID; это не существующий проверенный объект. Его нельзя выдать читателю за рабочую загрузку. Для собственного упражнения следует отдельно сохранить изображение, получить настоящий адрес выбранного хранилища и зафиксировать ожидаемые байты.
Клиент сначала читает tokenURI, затем получает описание, затем загружает image. Это несколько независимых запросов. Успешный ownerOf не означает успешную картинку, а недоступное изображение не отменяет существование токена. Экрану нужны разные состояния ошибок для цепочки и внешних файлов.
Неизменный URI и изменяемый сервер
Если строка равна https-адресу, отсутствие setter в контракте фиксирует только адрес. Владелец сервера может изменить ответ по тому же URL. Пользователь увидит новую картинку при прежнем tokenURI. Поэтому заявление «метаданные неизменны» нуждается в уточнении того, какие именно байты защищены.
Адрес содержимого, например подходящий IPFS CID, связывает ожидаемый объект с его представлением. Но наличие CID ещё не обеспечивает, что кто-то хранит и отдаёт объект. Неизменность и доступность решаются отдельно. Команда документирует сохранение, закрепление и способы получения без обещания магического вечного хранения.
Описание также может ссылаться на изменяемый HTTPS image. Даже если сам JSON закреплён содержимым, зависимость изображения остаётся отдельной. Для действительно фиксированного набора нужно рассмотреть все связанные файлы, а не только верхний JSON. В практической библиотеке такой разбор похож на фиксацию веб-манифеста и его ресурсов.
Метаданные не являются полномочием
Поле description с текстом «администратор» не назначает роль AccessControl. Поле attributes с названием курса не меняет CourseCatalog. Значения предназначены для отображения, пока отдельный проверенный код явно не использует их как часть доверенной политики.
Нельзя выдавать доступ по произвольному JSON, загруженному от владельца токена, если он способен поменять этот JSON. Иначе пользователь сам напишет нужное условие. Для ограничения услуги приложение проверяет определённый контракт, адрес, текущую собственность и правила продукта, а отображаемый текст остаётся вторичным.
В нашем образце администратор выбирает URI при выпуске. Это доверенная редакционная операция. Если появится разрешение пользователю выпускать собственные описания, HTML-интерфейс должен безопасно выводить текст и проверять обработку внешних ссылок. Стандарт токена не устраняет обычные риски отображения непроверенного веб-контента.
Публикация и исправление ошибки
После успешного выпуска показанный контракт не позволяет исправить неверный URI. Такая жёсткость может быть полезна для подтверждённого набора, но неудобна для опечатки. Выбор делается до публикации: обновляемые метаданные, фиксированные метаданные или новая коллекция с миграцией.
Если требуется обновление, следует определить субъект setter, событие изменения и то, что читатель увидит как историю. Наличие административной кнопки означает доверие к будущим правкам. Нельзя одновременно обещать отсутствие влияния администратора и предоставить ему незаметную замену описания.
Проведите мысленный разбор трёх случаев. В первом URI непустой, но сервер отвечает ошибкой. Во втором JSON доступен, а image отсутствует. В третьем администратор пытается второй раз присвоить URI уже существующему номеру. Показанный интерфейс не содержит такой операции: новый issue выдаёт следующий tokenId. Это помогает отделить ошибку ресурса от допустимых переходов состояния коллекции.
Сохраните для собственного примера адрес коллекции, номер, полный URI и ожидаемую форму ответа. Если файл рассчитан как часть веб-манифеста библиотеки, используйте согласованный SHA-256 точных сохранённых байтов. Не объявляйте строку tokenURI автоматически тем же digest: это разные представления и разные проверяемые свойства.
CID и контрольная сумма файла
CID имеет собственное представление с информацией о кодировании и организации данных. Нельзя просто подставить SHA-256 файла в ipfs:// и объявить адрес готовым. Файл может разбиваться на блоки и описываться структурой, поэтому digest внутри CID в общем случае отличается от непосредственной контрольной суммы всех байтов файла. Документация IPFS разбирает это различие отдельно.
Для нашей библиотеки храните две явно подписанные величины: URI получения и SHA-256 сохранённого манифеста. Они не обязаны иметь одинаковую форму. Если изменился способ импорта в IPFS, CID может измениться при прежнем исходном файле, а проверка его точных байтов продолжит использовать прежний SHA-256. Это не противоречие, а два договора представления.
Клиент также должен ограничивать допустимые схемы URI и способ обработки ответа. Строка из контракта является данными, а не командой выполнить произвольный JavaScript. При выводе description используйте текстовое представление либо осознанный безопасный формат. Собственный фиксированный контракт не гарантирует безопасность всех будущих коллекций, которые интерфейс решит поддержать.
Для редакционной процедуры подготовьте ресурс до issue, откройте его ожидаемым клиентом и запишите зависимые файлы. В этой задаче действия не выполняются; мы описываем будущий порядок проверки и не заменяем его утверждением о якобы проверенном URI.
Для самостоятельного уточнения: ERC721.sol (5.4.0), eip-721, content-addressing.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.