ERC-721 и NFT
ERC-721 — стандарт уникальных токенов: у каждого свой id и владелец. Это технология за NFT.
Если ERC-20 — это рубли (любая купюра равна другой), то ERC-721 — это билеты с местами: каждый уникален и принадлежит ровно одному.
ERC-721 описывает невзаимозаменяемые (non-fungible) токены. Каждый токен — это tokenId (уникальное число) со своим владельцем. Ключевые функции: ownerOf(tokenId) (кто владеет), balanceOf(owner) (сколько NFT у адреса), transferFrom/safeTransferFrom (передать конкретный токен), approve/setApprovalForAll (разрешения), и tokenURI(tokenId) — ссылка на метаданные (картинку, имя, атрибуты).
ERC-20 vs ERC-721
===================
ERC-20: balanceOf[addr] = 100 (взаимозаменяемо, сумма)
ERC-721: ownerOf[1] = alice
ownerOf[2] = bob (каждый id уникален,
ownerOf[3] = alice у одного владельца)
tokenURI[1] -> ipfs://.../1.json (метаданные)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItem is ERC721, Ownable {
uint256 private _nextId;
constructor() ERC721("GameItem", "ITM") Ownable(msg.sender) {}
// только владелец контракта может чеканить новые NFT
function mint(address to) external onlyOwner returns (uint256 id) {
id = _nextId++;
_safeMint(to, id); // создаёт токен id и присваивает владельцу
}
}
Как работает под капотом (EVM/газ)
Внутри ERC-721 — маппинги tokenId => owner и owner => balance. Минт записывает владельца нового id и излучает Transfer от нулевого адреса (это и есть «рождение» токена). safeTransferFrom проверяет, что получатель-контракт умеет принимать NFT (реализует onERC721Received) — иначе перевод откатится, чтобы токен не «застрял». Метаданные (tokenURI) обычно не лежат в storage целиком: там хранят лишь ссылку (часто на IPFS), а саму картинку — вне сети, потому что хранить байты изображения on-chain неподъёмно дорого.
# Та же логика на Python: ядро ERC-721
owner_of = {} # tokenId -> owner
balance_of = {} # owner -> count
token_uri = {} # tokenId -> ссылка на метаданные
next_id = 0
def mint(to, uri):
global next_id
tid = next_id; next_id += 1
owner_of[tid] = to
balance_of[to] = balance_of.get(to, 0) + 1
token_uri[tid] = uri
print(f"Mint #{tid} -> {to}")
return tid
def transfer(frm, to, tid):
assert owner_of[tid] == frm, "REVERT: not owner"
owner_of[tid] = to
balance_of[frm] -= 1
balance_of[to] = balance_of.get(to, 0) + 1
t = mint("alice", "ipfs://meta/0.json")
transfer("alice", "bob", t)
print("ownerOf #0:", owner_of[0], "| baalice:", balance_of.get("alice", 0))
print("tokenURI #0:", token_uri[0])
«Та же логика на Python ▶». Каждый id уникален и принадлежит одному владельцу; перевод меняет владельца конкретного токена, а не «сумму», как в ERC-20.
Частые ошибки
- Хранить полную картинку on-chain — это запредельно дорого; хранят ссылку на IPFS/Arweave.
- Использовать
transferFromвместоsafeTransferFromпри отправке на контракт — токен может «застрять» у получателя, который не умеет его принять. - Минтить без контроля доступа — кто угодно начеканит себе NFT.
Best practices
- Наследуйте проверенный
ERC721от OpenZeppelin, а не пишите стандарт вручную. - Для пользовательских адресов и контрактов используйте
safeTransferFrom. - Метаданные кладите на децентрализованное хранилище (IPFS) и фиксируйте контент-хэшем, чтобы их нельзя было подменить.
approve, setApprovalForAll и маркетплейсы
Разрешения в ERC-721 работают похоже на ERC-20, но точечнее. approve(spender, tokenId) разрешает адресу распоряжаться одним конкретным токеном, а setApprovalForAll(operator, true) — сразу всеми вашими токенами этой коллекции. Именно второй вызов делают маркетплейсы вроде OpenSea: вы один раз разрешаете контракту площадки оперировать вашей коллекцией, и дальше он может перевести проданный NFT покупателю без отдельного approve на каждую сделку. Это удобно, но и рискованно: дав setApprovalForAll вредоносному или взломанному контракту, вы рискуете всей коллекцией сразу. Поэтому действует то же правило, что и с «бесконечным» allowance в ERC-20 — выдавайте операторские права только проверенным площадкам и периодически отзывайте старые разрешения. Расширение ERC721Enumerable добавляет перебор токенов, но дорого по газу, поэтому в продакшене перечисление чаще строят через события и индексатор.
Итоги
ERC-721 даёт уникальные токены с владельцем и метаданными; это фундамент NFT. От ERC-20 он отличается тем, что оперирует tokenId, а не суммой. Дальше — самый важный раздел: безопасность смарт-контрактов.