ERC-721 is the Ethereum standard that makes NFTs (Non-Fungible Tokens) possible. It defines how unique digital assets are created, owned, and transferred on the blockchain. From digital art and collectibles to real estate deeds and gaming items, ERC-721 provides the technical foundation for representing ownership of one-of-a-kind assets. This guide explains how the standard works, its required functions, metadata handling, and how it compares to other token standards.
What are NFTs and Why Do They Need a Standard?
A Non-Fungible Token (NFT) is a blockchain-based token that represents a unique asset. Unlike ERC-20 tokens where every unit is identical, each NFT has a distinct token ID that differentiates it from all others in the same collection.
Before ERC-721, early NFT projects like CryptoPunks used custom contracts with non-standard interfaces. This made it difficult for wallets, marketplaces, and other applications to interact with them. ERC-721, proposed by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs in January 2018 (EIP-721), established a universal interface that all NFT contracts could implement.
With a standard interface, any marketplace (like OpenSea or Blur) can list any ERC-721 token, any wallet (like MetaMask) can display it, and any smart contract can interact with it — without needing custom integration code for each collection.
The ERC-721 Interface
The ERC-721 standard defines the following required functions and events that every compliant contract must implement:
Ownership Functions
// Returns the owner of a specific token
function ownerOf(uint256 tokenId) external view returns (address);
// Returns the number of tokens owned by an address
function balanceOf(address owner) external view returns (uint256);Unlike ERC-20 where balanceOf returns a token amount, ownerOf is the primary query function in ERC-721, returning the specific address that owns a given token ID.
Transfer Functions
// Safe transfer - checks if receiver can handle NFTs
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) external;
// Unsafe transfer - does not check receiver
function transferFrom(address from, address to, uint256 tokenId) external;The safeTransferFrom function is preferred because it checks whether the recipient address (if it is a contract) implements the IERC721Receiver interface. This prevents tokens from being permanently locked in contracts that cannot handle them.
Warning: Using transferFrom to send an NFT to a contract that does not implement IERC721Receiver will permanently lock the token. Always use safeTransferFrom unless you have a specific reason not to.
Approval Functions
// Approve a single token to a specific address
function approve(address to, uint256 tokenId) external;
// Approve or revoke an operator for ALL tokens
function setApprovalForAll(address operator, bool approved) external;
// Get the approved address for a single token
function getApproved(uint256 tokenId) external view returns (address);
// Check if an operator is approved for all tokens of an owner
function isApprovedForAll(address owner, address operator) external view returns (bool);The approval system in ERC-721 is more nuanced than ERC-20. You can approve a specific address for a single token, or use setApprovalForAll to grant an operator (like a marketplace) permission to transfer any of your tokens. Marketplaces rely on setApprovalForAll so they can execute sales without requiring a new approval for each listing.
Required Events
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);Token URI and Metadata
The ERC-721 standard includes an optional metadata extension that most collections implement. The key function is tokenURI(uint256 tokenId), which returns a URL pointing to a JSON file describing the token.
// ERC-721 Metadata JSON Schema
{
"name": "CryptoKitty #42",
"description": "A unique digital kitten.",
"image": "ipfs://QmXyz.../42.png",
"attributes": [
{ "trait_type": "Fur", "value": "Calico" },
{ "trait_type": "Eyes", "value": "Blue" },
{ "trait_type": "Generation", "value": 3, "display_type": "number" }
]
}Where is Metadata Stored?
The metadata JSON and associated media files can be stored in several ways, each with trade-offs:
| Storage | Permanence | Cost | Example |
|---|---|---|---|
| On-chain | Permanent | Very high | Base64 encoded SVG in contract |
| IPFS | Permanent (if pinned) | Low | ipfs://QmXyz... |
| Arweave | Permanent | One-time fee | ar://abc123... |
| Centralized (AWS, etc.) | Not guaranteed | Low | https://api.example.com/42 |
Decentralized storage (IPFS, Arweave) is strongly preferred because centralized servers can go offline, and the project team can modify or delete the metadata. On-chain storage is the most permanent but is only practical for small assets like SVGs.
How NFT Minting Works
Minting is the process of creating a new NFT on the blockchain. Under the hood, it assigns a new token ID to an owner address and emits a Transfer event from the zero address.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
uint256 private _nextTokenId;
string private _baseTokenURI;
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MINT_PRICE = 0.08 ether;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(uint256 quantity) external payable {
require(_nextTokenId + quantity <= MAX_SUPPLY, "Exceeds max supply");
require(msg.value >= MINT_PRICE * quantity, "Insufficient payment");
for (uint256 i = 0; i < quantity; i++) {
_safeMint(msg.sender, _nextTokenId);
_nextTokenId++;
}
}
function _baseURI() internal view override returns (string memory) {
return _baseTokenURI;
}
function setBaseURI(string memory baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
}Common minting patterns include public mints (anyone can mint), allowlist mints (using Merkle proofs to restrict access), and lazy minting (metadata is set at mint time). Gas-optimized implementations like ERC-721A allow minting multiple tokens in a single transaction at near the cost of minting one.
Notable NFT Collections
These collections helped define the NFT space and showcase different approaches to the ERC-721 standard:
| Collection | Supply | Notable Feature |
|---|---|---|
| CryptoPunks | 10,000 | Pre-ERC-721, later wrapped |
| Bored Ape Yacht Club | 10,000 | IP rights granted to holders |
| Azuki | 10,000 | Pioneered ERC-721A for gas savings |
| Art Blocks | Varies | Generative art, on-chain scripts |
| Nouns | Unlimited (1/day) | Fully on-chain SVG, DAO governance |
ERC-721 vs ERC-1155
ERC-1155 is a newer multi-token standard that can handle both fungible and non-fungible tokens in a single contract. Here is how they compare:
| Feature | ERC-721 | ERC-1155 |
|---|---|---|
| Token types | Non-fungible only | Fungible + non-fungible |
| Batch transfers | Not built-in | Native support |
| Gas per transfer | Higher | Lower (batch optimized) |
| Marketplace support | Universal | Widespread |
| Best for | 1-of-1 art, PFPs | Gaming items, editions |
| Complexity | Simpler | More complex |
Choose ERC-721 when each token needs to be truly unique (profile pictures, 1-of-1 art). Choose ERC-1155 when you need editions (multiple copies of the same item) or a mix of fungible and non-fungible tokens (like in-game currencies alongside unique weapons).
Gas Optimization Techniques
NFT minting and transfers can be expensive. Understanding how gas fees work is essential for optimizing costs. Here are proven techniques to reduce gas costs:
- ERC-721A: Developed by the Azuki team, this implementation allows minting multiple tokens for nearly the same gas cost as minting one. It uses lazy initialization of ownership data.
- Merkle tree allowlists: Instead of storing a list of allowed addresses on-chain (which is expensive), store only the Merkle root and let users provide proofs during minting.
- Lazy minting: Defer on-chain minting until the first buyer purchases the NFT. The creator signs the metadata off-chain, and the buyer pays the gas.
- Batch metadata reveals: Set all token URIs to a placeholder during minting, then update the base URI in a single transaction to reveal all metadata at once.
- L2 deployment: Deploy on Layer 2 networks (Arbitrum, Optimism, Base) where gas costs are significantly lower than Ethereum mainnet.
NFT Marketplaces
Marketplaces allow users to buy, sell, and discover NFTs. They interact with ERC-721 contracts through the standard interface, using approval functions to facilitate sales:
- OpenSea: The largest NFT marketplace by volume. Uses the Seaport protocol for order matching.
- Blur: A trader-focused marketplace with zero platform fees and advanced portfolio management tools.
- Foundation: Curated platform focused on digital art with auction-based sales.
- Magic Eden: Originally a Solana marketplace that expanded to support Ethereum, Polygon, and Bitcoin (Ordinals).
Frequently Asked Questions
What is the difference between ERC-721 and ERC-20?
ERC-20 tokens are fungible, meaning every token is identical and interchangeable (like dollars). ERC-721 tokens are non-fungible, meaning each token has a unique ID and can represent a distinct asset. ERC-20 tracks balances per address, while ERC-721 tracks ownership of individual token IDs.
What does "non-fungible" mean?
Non-fungible means unique and not interchangeable. A dollar bill is fungible because any dollar is worth the same as any other dollar. A painting is non-fungible because each painting is unique and has different value. NFTs bring this concept to digital assets by assigning unique token IDs on the blockchain.
Where is NFT metadata stored?
NFT metadata (name, description, image) can be stored on-chain (directly in the smart contract), off-chain on decentralized storage (IPFS, Arweave), or on centralized servers. The tokenURI function returns a URL pointing to a JSON file containing the metadata. IPFS and Arweave are preferred because they are immutable and censorship-resistant.
What is the difference between ERC-721 and ERC-1155?
ERC-721 assigns one unique owner per token ID, making it ideal for one-of-a-kind assets. ERC-1155 is a multi-token standard that supports both fungible and non-fungible tokens in a single contract, with built-in batch transfer support. ERC-1155 is more gas efficient for collections with many similar items.
Can NFTs be copied or duplicated?
While anyone can copy the image or media associated with an NFT, the blockchain ownership record cannot be duplicated. The smart contract maintains an immutable record of which address owns each token ID. Copying the image does not transfer ownership, just as photographing a painting does not make you the owner.
Try It Yourself
Want to decode NFT contract data? Use our ABI Encoder / Decoder to encode and decode ERC-721 function calls, or try the Calldata Decoder to inspect raw NFT transaction data.
Related Tools
- ABI Encoder / Decoder — Encode and decode smart contract function calls
- Calldata Decoder — Decode raw transaction calldata into readable parameters
- Keccak256 Hash Generator — Compute function selectors and event signatures
- Checksum Address Converter — Convert Ethereum addresses to EIP-55 format