Не взаимозаменяемые токены, tokenId, tokenURI, метаданные, safeTransferFrom, ERC-1155
NFT — это не картинка, это уникальный токен с доказуемым правом собственности на блокчейне
ERC-721 определяет стандарт невзаимозаменяемых (non-fungible) токенов. В отличие от ERC-20, каждый токен уникален и идентифицируется числовым tokenId. Нельзя поменять один NFT на другой "по номиналу" — у каждого своя история и метаданные.
Примеры применения: цифровое искусство (CryptoPunks, BAYC), игровые предметы, доменные имена (ENS), сертификаты, тикеты на события, deed на реальную собственность.
Минимальный стандарт ERC-721 включает:
interface IERC721 {
// Сколько NFT принадлежит owner
function balanceOf(address owner) external view returns (uint256 balance);
// Кто владеет tokenId
function ownerOf(uint256 tokenId) external view returns (address owner);
// Перевод NFT (небезопасный — не проверяет получателя)
function transferFrom(address from, address to, uint256 tokenId) external;
// Безопасный перевод — проверяет, что получатель умеет принимать NFT
function safeTransferFrom(address from, address to, uint256 tokenId) external;
// Разрешить конкретному адресу управлять одним tokenId
function approve(address to, uint256 tokenId) external;
// Разрешить/запретить адресу управлять ВСЕМИ NFT owner
function setApprovalForAll(address operator, bool approved) external;
// Кто имеет разрешение на конкретный tokenId
function getApproved(uint256 tokenId) external view returns (address operator);
// Проверить operator-разрешение
function isApprovedForAll(address owner, address operator) external view returns (bool);
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}Напишем базовую NFT-коллекцию с использованием OpenZeppelin:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract ArtCollection is ERC721, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public mintPrice = 0.05 ether;
// tokenId => URI метаданных
mapping(uint256 => string) private _tokenURIs;
constructor() ERC721("Art Collection", "ART") Ownable(msg.sender) {}
// Минт за ETH
function mint(string calldata tokenURI_) external payable returns (uint256) {
require(msg.value >= mintPrice, "Insufficient payment");
require(_tokenIds.current() < MAX_SUPPLY, "Max supply reached");
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
_safeMint(msg.sender, newTokenId);
_tokenURIs[newTokenId] = tokenURI_;
return newTokenId;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_ownerOf(tokenId) != address(0), "Token does not exist");
return _tokenURIs[tokenId];
}
// Вывод средств владельцем
function withdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}
}tokenURI(tokenId) — функция, возвращающая ссылку на JSON-файл с метаданными токена. Это может быть HTTP URL или IPFS URI.
Формат метаданных (OpenSea стандарт):
{
"name": "Art #1",
"description": "Unique digital artwork",
"image": "ipfs://QmHash.../1.png",
"attributes": [
{ "trait_type": "Background", "value": "Blue" },
{ "trait_type": "Rarity", "value": "Legendary" }
]
}Для децентрализации метаданных используют IPFS. IPFS адресует файлы по содержимому (CID — Content Identifier, хэш файла), а не по местоположению. Если файл изменить — CID изменится. Это гарантирует, что изображение NFT не будет подменено после минта.
На практике многие проекты хранят метаданные централизованно (сервер продавца), что нарушает принцип decentralization. Полноценная децентрализация требует IPFS + Filecoin или Arweave для постоянного хранения.
Разница критически важна при отправке NFT на смарт-контракты.
transferFrom просто перемещает токен — никакой проверки получателя. Если отправить NFT на контракт, который не умеет их обрабатывать, токен навсегда застрянет на этом адресе (нет метода для его вытащить).
safeTransferFrom перед завершением проверяет: если получатель — контракт, вызывает onERC721Received(operator, from, tokenId, data) и ожидает правильный return value (0x150b7a02). Если контракт не реализует этот интерфейс — транзакция откатывается, защищая токен от потери.
Правило: всегда используйте safeTransferFrom при неуверенности, что получатель — EOA.
ERC-1155 — расширение, позволяющее одному контракту управлять и взаимозаменяемыми, и невзаимозаменяемыми токенами. Каждый токен идентифицируется id, у него может быть произвольное amount.
Это эффективно для игр: предметы инвентаря (меч x1 — уникальный NFT; зелья x100 — взаимозаменяемые) хранятся в одном контракте. Один safeBatchTransferFrom вызов передаёт несколько типов токенов одновременно, экономя газ.
Выбор стандарта:
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.