ERC-721, according to the Ethereum Foundation’s EIP repository, is the Ethereum smart contract standard for non-fungible tokens, formalized as EIP-721 on January 24, 2018. Each token carries a unique uint256 identifier, and the pair of contract address and tokenId is globally unique across the Ethereum network, per Ethereum’s published proposal. Any wallet or indexer can therefore recognize any NFT in the same way, whether it represents art, a game item, or a deed.
Key Takeaways
- EIP-721 was authored by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs, per Ethereum Foundation records, created on January 24, 2018, with Final status on the Ethereum Improvement Proposals track.
- Every ERC-721 token has a unique tokenId, and according to Ethereum’s EIP-721 specification, the pair of contract address and tokenId is a globally unique, fully qualified identifier on an Ethereum chain.
- ERC-721 contracts must implement two interfaces: every compliant contract must implement both the ERC721 and ERC165 interfaces so wallets can detect support, per Ethereum developer documentation.
- Two optional extensions cover metadata and discovery: the metadata extension adds name(), symbol(), and tokenURI() functions, while the enumerable extension adds totalSupply(), tokenByIndex(), and tokenOfOwnerByIndex().
- ERC-1155 was built to address ERC-721’s one-contract-per-collection overhead, created on June 17, 2018, and designed so that a single deployed contract can include any combination of fungible tokens, non-fungible tokens, or other configurations.
How Does ERC-721 Work?
A numbered concert ticket is the closest everyday analogy. Every seat shares the same event template, yet seat A is not interchangeable with seat B. The same logic applies to digital assets: one smart contract issues many tokens, each with its own unique number, and only the current holder can transfer it.
1. Define the contract and token metadata
Every ERC-721 contract, per Ethereum’s specification, inherits the ERC-721 interface and must also implement ERC-165 so clients can query whether the contract supports a given interface. A minimal working contract with per-token metadata, according to OpenZeppelin documentation, uses OpenZeppelin’s ERC721URIStorage and calls the ERC721 constructor with a token name and symbol, for example, ERC721(“GameItem”, “ITM”). Both are set once at deploy time.
2. Mint a token with a unique tokenId
According to OpenZeppelin documentation, OpenZeppelin’s GameItem example increments a _nextTokenId counter, calls _mint(player, tokenId), and then _setTokenURI(tokenId, tokenURI) so the token points to its off-chain metadata. Per Ethereum’s specification, once assigned, the tokenId SHALL NOT change for the life of the contract, which is why exchanges and Etherscan index tokens by (contract address, tokenId).
3. Track ownership via balanceOf and ownerOf
Two read-only functions answer the two basic questions about ownership. balanceOf(address _owner) returns a uint256 count of tokens held by that address, and ownerOf(uint256 _tokenId) returns the owner address for a specific tokenId. The Ethereum.org developer documentation lists both methods as part of the standard method set that every indexer and wallet expects.
4. Transfer tokens with safeTransferFrom
ERC-721 exposes two transfer functions: transferFrom(_from, _to, _tokenId) and safeTransferFrom(_from, _to, _tokenId, data), both marked payable. The “safe” variant checks that the receiving contract can actually handle ERC-721 tokens, which prevents the common early-NFT failure mode of tokens getting trapped in contracts that have no way to send them back out.
5. Approve operators with approve and setApprovalForAll
Marketplaces need permission to move tokens without taking custody. approve(address _approved, uint256 _tokenId) grants permission for a single token, while setApprovalForAll(address _operator, bool _approved) grants permission for every token the caller owns in the contract. Two helpers complete the permission model: getApproved(uint256 _tokenId) returns the approved address for a specific token, and isApprovedForAll(address _owner, address _operator) returns whether an operator has blanket approval.
6. Emit Transfer and Approval events for indexers
Every state change is announced on-chain. The Ethereum.org documentation lists three required events: Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId), Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId), and ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved). OpenSea, Etherscan, and MetaMask read these events to keep their views in sync.
Core function reference
| Function | Purpose | Return | Payable |
| balanceOf(address) | Count tokens owned by an address | uint256 | No |
| ownerOf(uint256 tokenId) | Return the owner of a tokenId | address | No |
| safeTransferFrom(from, to, tokenId) | Transfer with receiver check | void | Yes |
| transferFrom(from, to, tokenId) | Raw transfer, no receiver check | void | Yes |
| approve(approved, tokenId) | Grant permission for one token | void | Yes |
| setApprovalForAll(operator, bool) | Grant blanket permission | void | No |
| getApproved(tokenId) | Read single-token approval | address | No |
| isApprovedForAll(owner, operator) | Read blanket approval status | bool | No |
Source: EIP-721, Ethereum.org developer documentation
Why Does ERC-721 Matter?
The motivation text in the EIP spec states the purpose plainly. Per the Ethereum Improvement Proposal, a standard interface allows wallet/broker/auction applications to interoperate with any NFT on Ethereum. The line captures why a buyer today can still hold an older token in a newer wallet and list it on a marketplace that did not exist when the contract was deployed, and it shows the same pattern we see in regulated finance: once a wire format is standardized, tools compound on top of it faster than the underlying assets themselves grow.
The marquee collections show what settlement-grade adoption looks like. Yuga Labs’ Bored Ape Yacht Club deployed on Ethereum mainnet with a maxNftSupply parameter equal to approximately 10,000 and source code implementing ERC-165, ERC-721, and ERC-721 Enumerable via OpenZeppelin, with the contract created by the deploying wallet approximately 4 years 359 days before an April 2026 Etherscan fetch, i.e., on or about April 22, 2021. Chiru Labs’ Azuki collection comprises 10,000 items and has recorded roughly 829,300 ETH in lifetime trading volume on OpenSea, a figure that dwarfs most publicly traded fine-art auctions.
The standard also draws a clear line against ERC-20 imitations. EIP-20 is insufficient for tracking NFTs, per the EIP motivation, because each NFT is distinct, while each fungible token is identical. That line explains why every generative art, gaming, and identity on Ethereum today points back to EIP-721 rather than to earlier hand-rolled schemes.
Pros, Cons, and Risks
Advantages
- Globally unique asset identity. Every NFT is identified by a unique uint256 ID inside the ERC-721 smart contract, and that identifier cannot change for the life of the contract. Exchanges and indexers can use the pair of contract address and tokenId as a primary key.
- Broad wallet and tooling support via interface detection. Because ERC-721 contracts must implement ERC-165, any wallet or dApp can query supportsInterface at runtime to confirm a contract is NFT-compliant before interacting with it.
- Battle-tested reference implementations. The OpenZeppelin Contracts v5.x library packages ERC-721, ERC-721URIStorage, and related extensions as audited components that developers can inherit rather than writing from scratch.
Trade-offs and Risks
- On-chain metadata is expensive. OpenZeppelin’s documentation explicitly warns that storing all item information on-chain will be rather costly, so most contracts store metadata at an HTTP or IPFS URI via tokenURI.
- One contract per collection creates bytecode overhead. The EIP-1155 motivation states that token standards like ERC-20 and ERC-721 require a separate contract for each token type or collection, which places a lot of redundant bytecode on the Ethereum blockchain.
- No native royalty enforcement. The base standard has no concept of a creator royalty, which is why EIP-2981 emerged and why marketplace-level royalty bypass became a governance issue through 2022 and 2023.
- Off-chain tokenURI is a point of centralization. When a tokenURI resolves to an HTTP server that goes offline, the art tied to that token can disappear while the on-chain ownership record remains intact.
ERC-721 vs ERC-20 vs ERC-1155
ERC-721 sits in a three-standard family and is easiest to understand by contrast. OpenZeppelin frames the distinction cleanly: unlike ERC-20, ERC-721 lacks a decimals field, since each token is distinct and cannot be partitioned. ERC-20 tracks a balance as a single number per address, ERC-721 tracks a mapping from tokenId to owner, and the ERC-1155 multi-token standard includes any combination of fungible tokens, non-fungible tokens, or other configurations inside one contract.
| Standard | Fungibility | Per-token ID | Batch transfers | Typical use cases | Created |
| ERC-20 | Fungible | No, single balance per address | No | Payment tokens, stablecoins, governance tokens | 2015 |
| ERC-721 | Non-fungible | Yes, uint256 tokenId | No | Art, collectibles, membership passes, deeds | January 24, 2018 |
| ERC-1155 | Mixed (fungible, non-fungible, semi-fungible) | Yes, token type id | Yes, multi-type in one call | Gaming assets, editions, packs | June 17, 2018 |
Sources: EIP-721, EIP-1155, OpenZeppelin Contracts documentation
Batch efficiency is why games and editions favor the newer standard. The EIP-1155 motivation notes that new functionality is possible with this design, such as transferring multiple token types at once, saving on transaction costs, which matters when a game updates dozens of items for a player in one action. For one-of-one art or a single deed, batching adds nothing, and ERC-721 remains the default choice for developers.
Real-World Applications
Generative art and PFP collections
Profile-picture collections are the visible edge of the ecosystem. The BoredApeYachtClub contract on Ethereum mainnet, at address 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d, exposes a maxNftSupply parameter equal to 10,000 and was deployed by its wallet approximately 4 years 359 days before an April 2026 Etherscan fetch. Azuki, deployed at 0xed5af388653567af2f388e6224dc7c4b3241c544, has traded roughly 829,300 ETH in lifetime volume through OpenSea. For context on record sales, see the largest NFT sales on record.
Gaming items and on-chain assets
Games use the standard when they want per-item scarcity rather than per-balance scarcity. Gods Unchained Cards, a trading card game on the Ethereum blockchain, provides real ownership to in-game assets, and Sorare, a global fantasy football game, mints individual cards the same way. A specific card in one wallet cannot be merged with any other. Studios that need batch operations across many players typically pair this standard with the multi-token standard for consumable items, a layered pattern our gaming coverage has tracked across recent launches.
Identity and naming
Ethereum Name Service (ENS) issues every .eth name as a non-fungible token, making the name transferable, inheritable, and compatible with any NFT wallet. POAP, the proof-of-attendance protocol, issues badges the same way. Ethereum.org’s developer documentation names CryptoKitties, Sorare, the Ethereum Name Service, POAP, Gods Unchained Cards, and the Bored Ape Yacht Club as representative ERC-721 deployments. For broader context, see the history of NFTs and NFT market growth data.
The CryptoPunks exception: a canonical NFT that is not, technically, ERC-721
Nearly every glossary lists CryptoPunks as a flagship ERC-721 example. The on-chain contract tells a different story. The original Larva Labs CryptoPunks contract, CryptoPunksMarket at 0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb, is registered on Etherscan as an ERC-20 token with 0 decimals and a max total supply of 10,000. The reason is timing: CryptoPunks launched on Ethereum in June 2017, and the ERC-721 standard was first proposed in late 2017 by Dieter Shirley, then formalized in January 2018. Larva Labs built their own ownership scheme by adapting ERC-20, which is why each punk trades as a whole, indivisible unit, despite living in a fungible-token contract.
To trade punks on modern ERC-721 marketplaces, holders route them through a Wrapped CryptoPunks bridge contract that reissues each punk as an ERC-721 token, and the wrapping is reversible. The mismatch matters for data analysts: any on-chain dashboard filtering only for ERC-721 Transfer events misses every native CryptoPunk trade, which skews historical NFT-volume totals downward unless Wrapped CryptoPunks is explicitly included.
Scenario: Minting and transferring a single token
Imagine a studio mints a collectible badge for a customer named Alice.
- The studio deploys a contract inheriting OpenZeppelin’s ERC721URIStorage with ERC721(“StudioBadge”, “SB”).
- The studio calls mint with Alice’s address and a tokenURI. The contract assigns a new tokenId to Alice and emits a Transfer event from the zero address.
- A block explorer indexes the event, and Alice’s wallet shows the new token under StudioBadge.
- Alice lists the badge on a marketplace. The marketplace calls setApprovalForAll from Alice’s wallet, recorded on-chain as an ApprovalForAll event.
- A buyer named Bob pays the listing price. The marketplace calls safeTransferFrom from Alice to Bob. The contract emits a Transfer event and updates the ownerOf record from Alice to Bob.
- Any wallet querying ownerOf sees Bob instantly, because the state change settles at Ethereum block-level finality.
Change the function signatures, and no existing marketplace or wallet can interact with the contract.
Frequently Asked Questions (FAQs)
ERC-721 tokens are non-fungible and lack a decimals field since each token is distinct and cannot be partitioned, while ERC-20 tokens are fungible: every unit is interchangeable with every other inside the same contract. Think of the fungible standard as currency and the non-fungible standard as a titled asset with its own tokenId and metadata record.
EIP-721 was authored by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs, and the proposal was created on January 24, 2018. It now holds Final status on the Ethereum Improvement Proposals track. Dieter Shirley first proposed the NFT standard in late 2017, before the CryptoPunks team had formalized their own ownership system.
A tokenId is a uint256 integer that uniquely identifies one NFT inside a single contract. The pair (contract address, uint256 tokenId) is a globally unique, fully qualified identifier for a specific asset on an Ethereum chain, and the value cannot change for the life of the contract. Marketplaces index listings by contract and tokenId, never by token name.
Not natively. The original CryptoPunks contract deployed by Larva Labs in June 2017 is registered on Etherscan as an ERC-20 token with 0 decimals, because it predates the ERC-721 standard proposed in late 2017. Users can wrap their punks through the Wrapped CryptoPunks bridge, which reissues each punk as an ERC-721 token and can be reversed. Marketplaces commonly show the wrapped version.
It depends on the use case. ERC-1155 was designed so that a single deployed contract can include any combination of fungible tokens, non-fungible tokens, or other configurations, and supports transferring multiple token types at once to save on transaction costs. For games that mint many items per player, the multi-token standard wins on gas. For single-edition art, memberships, or deeds, the original non-fungible standard remains the default choice.
Conclusion
ERC-721 is the rail that turned a unique digital asset on Ethereum into an interoperable market. The standard’s core guarantee is simple: the pair of contract address and uint256 tokenId is a globally unique, fully qualified identifier for a specific asset on an Ethereum chain, and from that one invariant, everything else follows. Every compliant wallet or explorer reads the same set of function signatures, including balanceOf, ownerOf, safeTransferFrom, transferFrom, approve, setApprovalForAll, getApproved, and isApprovedForAll, plus the Transfer, Approval, and ApprovalForAll events.
The ecosystem keeps evolving. ERC-1155 followed in June 2018 to address batch efficiency, royalty extensions arrived later, and account-abstraction wallets are changing how transfers are authorized this year. Our NFT coverage tracks a consistent pattern: the protocols that survive preserve the original ERC-721 interface rather than replace it, because the tooling debt of changing a wire format always outweighs the perceived gain. The tokenId that mattered in 2018 still matters this year, and on current trajectories, it will still resolve cleanly in the wallet your grandchildren inherit.