How to build and use ERC-721 tokens in 2021
An intro for devs to the uniquely identifying token standard and its future
The ERC-721 standard has been around for a while now. Originally made popular by blockchain games, it's more and more used for other applications like Defi.
But what exactly is it?

A non-fungible token (NFT) is a uniquely identifying token. The word non-fungible implies you cannot just replace one NFT with another one. Every NFT is unique and different. In contrast to any fungible token like Bitcoin or ERC-20 where it doesn't matter which specific coin/token you receive, they all have the same value.
On Ethereum Cryptokitties helped to make this into the now popular 721 standard.
The Features of ERC-721
Let's explore the exact definition of the standard, all features and functions that are part of it.
1. The NFT implementation
At the core we have a mapping from uint256 => address. This is the reverse of a fungible token (e.g. ERC-20) mapping which is from address => uint256.
Why this reverse mapping?
In the fungible tokens we map from owner to balance. While in 721 we actually also do this in a second mapping (see balanceOf
below), we more importantly need a way to uniquely identify tokens. By mapping from uint256 to address, we can give every minted token an id and map this id to exactly one owner. When creating a new NFT token, you create a new id (most commonly you just start at id 1 and count + 1 any following id) and set the owner as ownerMapping[id] = receiverAddress.
Let's look at the actual 721 functions:
A. Getters
function ownerOf(uint256 tokenId) external view returns (address);
Returns the owner of the NFT token with the given id. In the 721 implementation we would just return ownerMapping[id
]
here.
function balanceOf(address owner) external view returns (uint256);
Returns the balance of an owner, meaning the total amount of how many NFT tokens this address owns. In our implementation we will need to keep track of this when minting or transferring an NFT.
B. Transfer
function safeTransferFrom(address from, address to, uint256 tokenId) external payable;
The transfer function comes in three forms. This one here is probably the most commonly used one. It will transfer the token with the given id to the receiving address. It will only work if the from address is the owner or approved (see below for approvals) and if the receiver is either not a smart contract or a smart contract that implements the 721 receiver interface (see below for ERC-165 and how to receive tokens). Otherwise the call will revert.
The other two variants for the transfer functions are simply one with an additional bytes data field to pass to the receiver hook and another non-safe transfer function that doesn't invoke the transfer hook.
C. Approvals
function approve(address approveTo, uint256 tokenId) external payable;
function getApproved(uint256 tokenId) external view returns (address);
function setApprovalForAll(address operator, bool isApproved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
The approval functions allow someone to approve another address for either one specific token or all tokens. Think of the approve for all functions as something where you would approve a trusted smart contract, so it cannot just steal all your tokens. Once you approve another address, it has full transfer control over it.
2. Metadata (optional)
The metadata extension is optional for ERC-721 smart contracts. It includes a name and symbol just like they exist in many other token standards including ERC-20. Further a tokenURI
can be defined for a token. Each unique token should have its own URI.
interface ERC721Metadata {
function name() external view returns (string name);
function symbol() external view returns (string symbol);
function tokenURI(uint256 tokenId) external view returns (string);
}
This tokenURI
links to a metadata file. In the case of a game, the file could just be hosted on the servers of the game provider. In the spirit of decentralization, in many cases IPFS is used to store this file. Just make sure to use IPFS pinning not to loose the file. The metadata file has a defined name, description and image. It might look like this:
{
"name": "Buzz",
"description": "Paper collage, using salvaged and original watercolour papers",
"image": "https://ipfs.infura.io/ipfs/QmWc6YHE815F8kExchG9kd2uSsv7ZF1iQNn23bt5iKC6K3/image"
}
3. Enumeration (optional)
Another additional optional interface is the enumeration. It contains functions for counting and receiving tokens by the index.
interface ERC721Enumerable {
function totalSupply() external view returns (uint256);
function tokenByIndex(uint256 _index) external view returns (uint256);
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}
With the totalSupply
function you can determine how many NFT's in total exist currently, excluding the burnt ones of course. The other two functions will return the n'th token from the list of all tokens (tokenByIndex
) or from the list of the tokens of that owner (tokenOfOwnerByIndex
).
You might have noticed, if you never burn tokens and count token ids from 1 incrementally, the first two functions would just reflect the token ids.
4. Receiver hook and ERC-165 support
If you don't know what EIP-165 is yet, see my tutorial here. In short it's a way for smart contracts to define what interfaces they support. So for example, does the smart contract support receiving ERC-721 tokens? If it does, the respective supportsInterface
function must exist and return true for that contract.
In the ERC-721 a smart contract receiver must implement the onERC721Received
function. You could use the onERC721Received
function to implement some further receiving logic or just confirm the receiver supports the 721. But be aware that in the case of the non-safe transfer function, the hook is not called! In the hook you always need to return the magic value as confirmation.
How can I deploy a 721 NFT?
Now let's see how you would implement a 721 token contract. We will be using the Openzeppelin contracts to help us.
This is a basic NFT example contract which allows the owner to mint new tokens. The syntax for the imports assumes using Remix, adjust it in case you installed the Openzeppelin contracts via npm.
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/utils/Counters.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/access/Ownable.sol";
contract NftExample is ERC721, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("NFT-Example", "NEX") {}
function mintNft(address receiver, string memory tokenURI) external onlyOwner returns (uint256) {
_tokenIds.increment();
uint256 newNftTokenId = _tokenIds.current();
_mint(receiver, newNftTokenId);
_setTokenURI(newNftTokenId, tokenURI);
return newNftTokenId;
}
}
Consider using some other minting mechanism depending on your use case. The _setTokenURI is of course optional.
You can also choose to use the Preset available here. A simple contract MyContract is ERC721PresetMinterPauserAutoId
will be all you need to get an NFT contract that is preset to have
- minting, pausing and admin roles using the Openzeppelin Access Control mechanism
- enabled pause and unpause functionality
I highly recommend checking out the Openzeppelin 721 contracts and documentation for further details.
How can I receive an NFT in a contract?
As mentioned before, if you use the safeTransferFrom function to send an NFT to a smart contract, it will revert unless the contract specifically added support for receiving 721 tokens
Let's see how you would add this kind of support:
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721Holder.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721Receiver.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/introspection/ERC165.sol";
contract Receiver721Example is IERC721Receiver, ERC165, ERC721Holder {
constructor() {
_registerInterface(IERC721Receiver.onERC721Received.selector);
}
function doSomethingWith721Token(IERC721 nftAddress, uint256 tokenId) external {
// do something here
}
}
The available ERC721Holder
is implementing a basic onERC721Received
function which returns the magic value to confirm the support for receiving 721 tokens. Make sure to actually implement a function that can handle the token accordingly.
Use cases for NFT and ERC-721

Most commonly people use the standard for blockchain games like the original Cryptokitties. It's well suited for this case as you can easily have in-game items represented as NFT. The additional metadata file is particularly useful to add more in-game information to the NFT.
But the usage goes beyond just games, and we are seeing it come into existence more and more now. Take a look at Rarible to see many other use cases. Digital art seems to be a major driver for NFT's at this point. Domains can be purchased or utilities, e.g., something that gives you digital access rights. More marketplaces include OpenSea, SuperRare, and Axie Infinity.
I find those in the space of Defi very interesting. For example you can purchase certain insurances, e.g., using yinsure (from yearn). The NFT gives you the permission for the defined insurance and claims are handled and paid out by the decentralized governance.
We even considered using the 721 standard for our derivatives as tokenized positions at Injective Protocol, however we ultimately decided against it as it wasn't a perfect fit. In theory it would have been possible though and might be something to add later on if people show interest.
In general any uniquely identifying possession could be turned into an NFT. We will see much more use cases in the future.
Future and Interchain NFT's

As mentioned we will probably see much more use cases for NFT's in the future on Ethereum and other blockchains. There is a new standard in the making called Interchain NFTs. The Interchain Foundation from the Cosmos universe is developing this standard for multiple networks dealing with 721 NFTs. It will be a useful standard for any blockchain once they have working bridges.
What are you looking forward to? Do you own any NFT's? Have you developed your own?
Let me know in the comments.
Solidity Developer
More great blog posts from Markus Waas
How to use ChatGPT with Solidity
Using the Solidity Scholar and other GPT tips
How to integrate Uniswap 4 and create custom hooks
Let's dive into Uniswap v4's new features and integration
How to integrate Wormhole in your smart contracts
Entering a New Era of Blockchain Interoperability
Solidity Deep Dive: New Opcode 'Prevrandao'
All you need to know about the latest opcode addition
How Ethereum scales with Arbitrum Nitro and how to use it
A blockchain on a blockchain deep dive
The Ultimate Merkle Tree Guide in Solidity
Everything you need to know about Merkle trees and their future
The New Decentralized The Graph Network
What are the new features and how to use it
zkSync Guide - The future of Ethereum scaling
How the zero-knowledge tech works and how to use it
Exploring the Openzeppelin CrossChain Functionality
What is the new CrossChain support and how can you use it.
Deploying Solidity Contracts in Hedera
What is Hedera and how can you use it.
Writing ERC-20 Tests in Solidity with Foundry
Blazing fast tests, no more BigNumber.js, only Solidity
ERC-4626: Extending ERC-20 for Interest Management
How the newly finalized standard works and can help you with Defi
Advancing the NFT standard: ERC721-Permit
And how to avoid the two step approve + transferFrom with ERC721-Permit (EIP-4494)
Moonbeam: The EVM of Polkadot
Deploying and onboarding users to Moonbeam or Moonriver
Advanced MultiSwap: How to better arbitrage with Solidity
Making multiple swaps across different decentralized exchanges in a single transaction
Deploying Solidity Smart Contracts to Solana
What is Solana and how can you deploy Solidity smart contracts to it?
Smock 2: The powerful mocking tool for Hardhat
Features of smock v2 and how to use them with examples
How to deploy on Evmos: The first EVM chain on Cosmos
Deploying and onboarding users to Evmos
EIP-2535: A standard for organizing and upgrading a modular smart contract system.
Multi-Facet Proxies for full control over your upgrades
MultiSwap: How to arbitrage with Solidity
Making multiple swaps across different decentralized exchanges in a single transaction
The latest tech for scaling your contracts: Optimism
How the blockchain on a blockchain works and how to use it
Ultimate Performance: The Aurora Layer2 Network
Deploying and onboarding users to the Aurora Network powered by NEAR Protocol
What is ecrecover in Solidity?
A dive into the waters of signatures for smart contracts
How to use Binance Smart Chain in your Dapp
Deploying and onboarding users to the Binance Smart Chain (BSC)
Using the new Uniswap v3 in your contracts
What's new in Uniswap v3 and how to integrate Uniswap v3
What's coming in the London Hardfork?
Looking at all the details of the upcoming fork
Welcome to the Matrix of blockchain
How to get alerted *before* getting hacked and prevent it
The Ultimate Ethereum Mainnet Deployment Guide
All you need to know to deploy to the Ethereum mainnet
SushiSwap Explained!
Looking at the implementation details of SushiSwap
Solidity Fast Track 2: Continue Learning Solidity Fast
Continuing to learn Solidity fast with the advanced basics
What's coming in the Berlin Hardfork?
Looking at all the details of the upcoming fork
Using 1inch ChiGas tokens to reduce transaction costs
What are gas tokens and example usage for Uniswap v2
Openzeppelin Contracts v4 in Review
Taking a look at the new Openzeppelin v4 Release
EIP-3156: Creating a standard for Flash Loans
A new standard for flash loans unifying the interface + wrappers for existing ecosystems
Tornado.cash: A story of anonymity and zk-SNARKs
What is Tornado.cash, how to use it and the future
High Stakes Roulette on Ethereum
Learn by Example: Building a secure High Stakes Roulette
How to implement generalized meta transactions
We'll explore a powerful design for meta transactions based on 0x
Utilizing Bitmaps to dramatically save on Gas
A simple pattern which can save you a lot of money
Using the new Uniswap v2 as oracle in your contracts
How does the Uniswap v2 oracle function and how to integrate with it
Smock: The powerful mocking tool for Hardhat
Features of smock and how to use them with examples
Trustless token management with Set Protocol
How to integrate token sets in your contracts
Exploring the new Solidity 0.8 Release
And how to upgrade your contracts to Solidity 0.8
How to build and use ERC-1155 tokens
An intro to the new standard for having many tokens in one
Leveraging the power of Bitcoins with RSK
Learn how RSK works and how to deploy your smart contracts to it
Solidity Fast Track: Learn Solidity Fast
'Learn X in Y minutes' this time with X = Solidity 0.7 and Y = 20
Sourcify: The future of a Decentralized Etherscan
Learn how to use the new Sourcify infrastructure today
Integrating the 0x API into your contracts
How to automatically get the best prices via 0x
How to build and use ERC-777 tokens
An intro to the new upgraded standard for ERC-20 tokens
COMP Governance Explained
How Compound's Decentralized Governance is working under the hood
How to prevent stuck tokens in contracts
And other use cases for the popular EIP-165
Understanding the World of Automated Smart Contract Analyzers
What are the best tools today and how can you use them?
A Long Way To Go: On Gasless Tokens and ERC20-Permit
And how to avoid the two step approve + transferFrom with ERC20-Permit (EIP-2612)!
Smart Contract Testing with Waffle 3
What are the features of Waffle and how to use them.
How to use xDai in your Dapp
Deploying and onboarding users to xDai to avoid the high gas costs
Stack Too Deep
Three words of horror
Integrating the new Chainlink contracts
How to use the new price feeder oracles
TheGraph: Fixing the Web3 data querying
Why we need TheGraph and how to use it
Adding Typescript to Truffle and Buidler
How to use TypeChain to utilize the powers of Typescript in your project
Integrating Balancer in your contracts
What is Balancer and how to use it
Navigating the pitfalls of securely interacting with ERC20 tokens
Figuring out how to securely interact might be harder than you think
Why you should automatically generate interests from user funds
How to integrate Aave and similar systems in your contracts
How to use Polygon (Matic) in your Dapp
Deploying and onboarding users to Polygon to avoid the high gas costs
Migrating from Truffle to Buidler
And why you should probably keep both.
Contract factories and clones
How to deploy contracts within contracts as easily and gas-efficient as possible
How to use IPFS in your Dapp?
Using the interplanetary file system in your frontend and contracts
Downsizing contracts to fight the contract size limit
What can you do to prevent your contracts from getting too large?
Using EXTCODEHASH to secure your systems
How to safely integrate anyone's smart contract
Using the new Uniswap v2 in your contracts
What's new in Uniswap v2 and how to integrate Uniswap v2
Solidity and Truffle Continuous Integration Setup
How to setup Travis or Circle CI for Truffle testing along with useful plugins.
Upcoming Devcon 2021 and other events
The Ethereum Foundation just announced the next Devcon in 2021 in Colombia
The Year of the 20: Creating an ERC20 in 2020
How to use the latest and best tools to create an ERC-20 token contract
How to get a Solidity developer job?
There are many ways to get a Solidity job and it might be easier than you think!
Design Pattern Solidity: Mock contracts for testing
Why you should make fun of your contracts
Kickstart your Dapp frontend development with create-eth-app
An overview on how to use the app and its features
The big picture of Solidity and Blockchain development in 2020
Overview of the most important technologies, services and tools that you need to know
Design Pattern Solidity: Free up unused storage
Why you should clean up after yourself
How to setup Solidity Developer Environment on Windows
What you need to know about developing on Windows
Avoiding out of gas for Truffle tests
How you do not have to worry about gas in tests anymore
Design Pattern Solidity: Stages
How you can design stages in your contract
Web3 1.2.5: Revert reason strings
How to use the new feature
Gaining back control of the internet
How Ocelot is decentralizing cloud computing
Devcon 5 - Review
Impressions from the conference
Devcon 5 - Information, Events, Links, Telegram
What you need to know
Design Pattern Solidity: Off-chain beats on-chain
Why you should do as much as possible off-chain
Design Pattern Solidity: Initialize Contract after Deployment
How to use the Initializable pattern
Consensys Blockchain Jobs Report
What the current blockchain job market looks like
Provable — Randomness Oracle
How the Oraclize random number generator works
Solidity Design Patterns: Multiply before Dividing
Why the correct order matters!
Devcon 5 Applications closing in one week
Devcon 5 Applications closing
Randomness and the Blockchain
How to achieve secure randomness for Solidity smart contracts?