100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

ERC-721 NFT Standard

The standard for non-fungible tokens on Ethereum, covering unique token IDs, ownership and metadata, safe transfers with receiver hooks, and minting.

Security & StandardsIntermediate10 min readJul 10, 2026
Analogies

Non-Fungible Tokens Explained

ERC-721 is the standard for non-fungible tokens (NFTs), where each token is unique and identified by a distinct uint256 tokenId within a contract. Unlike ERC-20, where balances are simple quantities, an ERC-721 contract maps each individual token to exactly one owner. This makes NFTs suitable for representing one-of-a-kind assets: digital art, collectibles, in-game items, event tickets, or tokenized real-world property. Two ERC-721 tokens from the same contract are never interchangeable, because each carries its own identity, ownership record, and metadata.

🏏

Cricket analogy: A signed bat from a specific match-winning century is one-of-a-kind, unlike interchangeable runs, which is exactly what a unique ERC-721 tokenId represents.

Ownership and Metadata

The core ERC-721 functions include ownerOf(tokenId), which returns the current owner of a specific token, and balanceOf(owner), which returns how many tokens an address holds. The optional but ubiquitous ERC721Metadata extension adds name(), symbol(), and tokenURI(tokenId), which returns a URI, often pointing to JSON on IPFS or a server, describing the token's attributes and image. The on-chain contract stores ownership and the URI pointer, while the rich media and traits typically live off-chain. Marketplaces read tokenURI to render the artwork and properties buyers see.

🏏

Cricket analogy: A trophy cabinet register lists who holds each specific trophy (ownerOf) with a plaque describing it (tokenURI), the NFT's ownership and metadata lookups.

Safe Transfers and Approvals

ERC-721 offers two transfer functions: transferFrom, which blindly moves a token, and safeTransferFrom, which additionally checks that a recipient contract implements onERC721Received. This callback prevents tokens from being permanently trapped in contracts that cannot handle them. For delegated transfers, approve(spender, tokenId) authorizes one token, while setApprovalForAll(operator, true) grants an operator control over an owner's entire collection, the mechanism marketplaces like OpenSea use. Because safeTransferFrom hands control to the receiver's callback, it is also a reentrancy surface that must be guarded when combined with state changes.

🏏

Cricket analogy: A safe single where the non-striker confirms the run is on before committing mirrors safeTransferFrom checking the recipient can receive before the token crosses.

solidity
// 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; // auto-incrementing id counter

    constructor(address owner) ERC721("MyNFT", "MNFT") Ownable(owner) {}

    // Mint a new unique token to `to`, returning its id
    function mint(address to) external onlyOwner returns (uint256) {
        uint256 tokenId = _nextTokenId++;
        _safeMint(to, tokenId); // invokes onERC721Received if `to` is a contract
        return tokenId;
    }

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://QmBaseHash/"; // tokenURI = baseURI + tokenId
    }
}

ERC-721 supports optional extensions: ERC721Enumerable adds totalSupply and on-chain enumeration of all token IDs (at a gas cost), while ERC721URIStorage lets each token store an individual metadata URI instead of deriving it from a shared base URI. Choose extensions based on your query and storage needs.

Minting and Token IDs

Minting creates a new token by assigning a fresh tokenId to an owner, usually through OpenZeppelin's internal _mint or _safeMint. A common pattern is a monotonically increasing counter, so each mint gets the next sequential id, though ids can also be derived from hashes or provided by the caller. Because minting writes new storage (ownership mappings and possibly URIs), it is one of the more gas-intensive operations, which is why large drops sometimes use lazy minting or ERC721A-style optimizations for cheaper batch mints. Every mint emits a Transfer event from the zero address, signaling creation to indexers.

🏏

Cricket analogy: A squad-numbering system assigns each debutant a unique cap number, just as minting assigns a fresh tokenId and enumerable extensions let you list the whole squad.

Use safeTransferFrom and _safeMint when the recipient may be a contract, otherwise a token can be sent to a contract that lacks onERC721Received and be locked forever. But remember the receiver callback is a reentrancy vector: follow Checks-Effects-Interactions and guard state-changing mint or transfer functions.

  • ERC-721 defines non-fungible tokens where each unique tokenId maps to a single owner.
  • ownerOf and balanceOf track ownership; the Metadata extension adds name, symbol, and tokenURI.
  • Rich media and traits usually live off-chain (e.g. IPFS), with the contract storing only the URI pointer.
  • safeTransferFrom checks onERC721Received so tokens are not trapped in incompatible contracts.
  • approve authorizes a single token; setApprovalForAll grants an operator control over the whole collection.
  • Minting assigns a new tokenId (often via an incrementing counter) and emits Transfer from the zero address.
  • Receiver callbacks in safeTransferFrom and _safeMint are reentrancy surfaces that must be guarded.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#SolidityStudyNotes#ERC721NFTStandard#ERC#721#NFT#Standard#StudyNotes#SkillVeris#ExamPrep