ERC-721¶
ERC-721 is the standard for non-fungible tokens, where ERC-20 tracks an interchangeable quantity per address (see ERC-20), ERC-721 tracks ownership of individually distinct, non-interchangeable tokens, each identified by its own unique ID. This chapter covers the core interface and the specific mechanism (safe transfers) that ERC-721 has and ERC-20 doesn't.
The core interface¶
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IERC721 {
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function transferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address);
function setApprovalForAll(address operator, bool approved) external;
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);
}
Verified: this compiles cleanly with solc 0.8.26.
Fungible versus non-fungible, at the interface level¶
Notice balanceOf here returns how many distinct tokens an address owns (a count, not a fungible quantity) and ownerOf(tokenId) answers "who owns this specific token," a question that has no ERC-20 equivalent at all, since ERC-20 tokens have no individual identity to ask that question about. This is the precise, interface-level expression of fungibility: ERC-20 asks "how much," ERC-721 asks "which one, and who owns it."
Two levels of approval¶
ERC-721 has two distinct approval mechanisms, serving different scopes:
approve(to, tokenId): authorizes one specific address to transfer one specific token, directly analogous to ERC-20'sapprove, but scoped to a single token ID rather than a fungible amount.setApprovalForAll(operator, approved): authorizes an address to transfer any and every token the caller currently owns or will ever own, a much broader grant used by marketplaces (so a user doesn't need to submit a separate approval transaction for every individual NFT they want to list for sale).
safeTransferFrom: the mechanism ERC-20 lacks¶
This is the feature worth understanding precisely, since it addresses a real, documented failure mode. transferFrom moves a token unconditionally (if the recipient address is a contract with no logic to handle receiving ERC-721 tokens, the token can become permanently stuck, since that contract may have no function capable of transferring it back out. safeTransferFrom closes this gap: if the recipient is a contract, it requires that contract to implement onERC721Received (a callback the transfer calls and checks the return value of) and reverts the entire transfer if that check fails) a real, protocol-enforced safety check, not merely a convention, that ERC-20 has no equivalent of at all (recall Transfers: a standard ERC-20 transfer has no way to be rejected or checked by the recipient).
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
The callback must return a specific, fixed 4-byte value (the function selector of onERC721Received itself) to confirm the receiving contract genuinely implements this interface and intends to accept the token, returning anything else, or reverting, causes safeTransferFrom to revert the whole transfer, protecting against exactly the stuck-token scenario described above.
Common misconceptions¶
ERC-721 tokens are not stored "inside" the NFT contract in the sense of holding any media or file data directly. The contract stores only ownership records (which address owns which token ID) and, typically, a URI pointing to metadata describing the token, covered fully in NFT Metadata; the actual image, video, or other asset an NFT is often associated with usually lives entirely off-chain.
transferFrom on an ERC-721 contract is not deprecated or unsafe to use in every context. It remains valid, standard-compliant, and appropriate when the sender knows the recipient can definitely handle the token (an ordinary externally-owned account, for instance, which has no onERC721Received requirement at all since the safety concern is specifically about contract recipients); safeTransferFrom is the more cautious default specifically for transfers to addresses whose ability to handle the token isn't already known.
Further reading¶
← Previous: Allowances and Approvals · Back to Tokens · Next: ERC-1155 →