Table of Contents

Introduction

The ERC-721 standard has redefined how ownership of both virtual and real-world assets is tracked, transferred, and traded on the blockchain. 

It has been adopted across industries, from virtual collectibles to real-world assets like art, music, and real-estate.

In this article, we’ll break down what ERC-721 is, the problems it solves, its impact, and how to get started with implementing it.

What is ERC-721

ERC-721 is the technical standard that defines how smart contracts implement, track, and transfer non-fungible tokens (NFTs) on the Ethereum and other EVM-compatible blockchains. 

Fungibility is the ability to exchange or replace an asset with another of the same kind and equal value. For example, dollar bills are fungible. If Alice has a $10 bill, she can exchange it with Bob for another $10 bill, because both have the same value.

A non-fungible asset, on the other hand, has unique characteristics and is not interchangeable with another asset. A piece of art owned by Alice is unique and does not have the same value as a different piece of artwork owned by Bob.

Illustration describing the difference between fungible and non-fungible assets.

What problem does the ERC-721 solve?

Before ERC-721, ERC-20 specified a standard API for how smart contracts implement, transfer, and track fungible tokens like Ether (ETH) and USDC. ERC-20 also specified an interface that allows wallets and exchanges to interact with and trade these tokens. 

However, ERC-20 didn’t account for use cases where developers want a token to represent a unique asset. This limitation made it impossible to represent or trade unique items like houses, artworks, or digital collectibles using ERC-20.

ERC-721’s solution

ERC-721 solves the problem of uniqueness by introducing a standard interface that allows you to implement, transfer, and track tokenized proof of ownership. To make integration with wallets and exchanges easier, ERC-721 builds on the existing design of ERC-20.

The key difference between ERC-721 and ERC-20 tokens is that every ERC-721 token has both a tokenId and a contract address while ERC-20 tokens have only a contract address. The tokenId parameter allows ERC-721s to be differentiated from other individual tokens.

How does ERC-721 work?

For a token to be an NFT, the contract that creates it must implement both ERC-721 and ERC-165 interfaces.

ERC-165 enables contracts to declare their compatibility with ERC-721 which allows other contracts or wallets to check for compatibility before initiating a transfer. Without ERC-165, if an NFT supporting the ERC-721 standard is transferred to a smart contract that doesn't support it, the token may get stuck in the unsupported contract. 

Here is an example implementation of the ERC-165’s supportsInterface function from OpenZeppelin that allows a contract to make a compatibility check:

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
   return
       interfaceId == type(IERC721).interfaceId ||
       interfaceId == type(IERC721Metadata).interfaceId ||
       super.supportsInterface(interfaceId);
}

A series of functions are required from both the ERC-721 and ERC-165 standards to manage NFTs.

These functions ensure that each token within the contract is unique, with its own individual metadata—such as the name, image, and description—and that the token can be safely transferred to users within the contract and users in other contracts that support the ERC-721 standard.

When a new NFT is minted, the contract that creates it assigns a unique tokenId to the NFT address. You can use the OpenZeppelin’s Counter utility to confirm the uniqueness of a token.

Below is an example of how we can assign a unique tokenId to an NFT while minting it using OpenZeppelin Counters utility.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract SimpleERC721Token is ERC721, Ownable {
   using Counters for Counters.Counter;
   Counters.Counter private _tokenIds;

   constructor() ERC721("Updraft NFT", "Updraft") Ownable(msg.sender) {}

   /**
    * @dev Function to mint a new token.
    * This function can only be called by the owner of the contract (due to the onlyOwner modifier).
    * @param to The address that will own the newly minted token.
    * @return The ID of the newly minted token.
    */
   function mintToken(address to) public onlyOwner returns (uint256) {
       // Increment the token ID counter to get a new unique ID
       _tokenIds.increment();

       // Get the current token ID
       uint256 newItemId = _tokenIds.current();

       // Mint the token to the specified address with the new token ID
       _mint(to, newItemId);

       // Return the newly minted token's ID
       return newItemId;
   }
}

ERC-721 implementation

To create a non-fungible token using the ERC-721 standard, you need to implement the the following required functions:

function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
function supportsInterface(bytes4 interfaceID) external view returns (bool);

And the required events in the interface:

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);

Now, let’s categorize the functions according to their purpose:

Token ownership functions

The function balanceOf returns the number of tokens owned by a specific address, while ownerOf returns the address that owns a particular tokenId. These make ownership details easily accessible for any given NFT.

NFT transfer

The functions transferFrom and safeTransferFrom are used to transfer tokens between addresses. 

The distinction is that safeTransferFrom checks whether the receiving contract supports ERC-721. 

safeTransferFrom also verifies if the address it is sending to is a contract address. If it is not a contract address, the call is not executed so tokens are not sent to incompatible addresses. On any transfer, a Transfer event is emitted to signal the change of ownership on-chain.

Delegation and approvals

ERC-721 provides approve and setApprovalForAll functions that allows a user to delegate control of their NFT to an operator. 

The approve function allows a specific address to manage a particular token, while setApprovalForAll gives an operator full control over all the owner’s tokens. 

These functions are essential for decentralized exchanges or marketplaces where users need to give temporary permissions for trading or listing NFTs. These actions trigger Approval or ApprovalForAll events.

These functions give you full management control and security over your NFTs. You can implement all the required functions yourself or choose to use well vetted and widely used implementations like OpenZeppelin and  Solmate.

An example of how to create a simple NFT using OpenZeppelin is shown below:

Image of OpenZeppelin's simple NFT implementation as seen in a Remix interface.

In the above image, you can see that only three lines of code are needed for a simple implementation. The rest of the implementations come from the OpenZepplin contract (ERC721) the contract MyNFT is inheriting from.

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
    constructor() ERC721("NFTName", "NFTSymbol") {}
}

In the sidebar of the Remix screenshot above, you’ll see every function implemented using the OpenZepplin library. You can immediately interact with your newly minted NFT and use all the available functions: transfer, approve an operator, check for supported interfaces, etc.

Impact of ERC-721

ERC-721 has significantly impacted several sectors that relate to asset ownership. Including digital collectibles, artwork, and other real-world assets like real estate.

The first major use case of NFTs was CryptoKitties in 2017. Launched before ERC-721 was finalized, the project was built on the principles that would later be standardized in ERC-721. 

CryptoKitties introduced blockchain-based gaming and demonstrated the potential of non-fungible tokens. It allowed users to buy, sell, and breed digital cats, each uniquely owned and verified on the Ethereum blockchain. It was so popular that it congested the Ethereum network.

Since then, new, innovative platforms that leverage ER721 have emerged such as OpenSea, Rarible, and Propy

They allow users, artists, and collectors to create, buy, and sell assets while maintaining proof of ownership on-chain and immutable forever.

The formalization of ERC-721 also opened new avenues of commerce for the gaming industry. Game designers can now build unique assets into their worlds that users can own and trade on platforms like Axie Infinity, Decentraland, and Immutable.

Platforms like Propy also make it easy to tokenize property ownership using NFTs. You can buy real-world properties (like houses and land) on the platform and your ownership is recorded on-chain and transferred to you. 

In 2023, development authorities in West Bengal, India, partnered with Polygon and Airchains to implement a blockchain-based land ownership and mutation system with NFTs.

The ERC-721 standard has also inspired other standards to improve or extend their functionality. Examples include: 

  • ERC-115 which allows you to create both fungible and non-fungible tokens and makes it easy to send multiple token types at once. 
  • ERC-6809 adds additional security to prevent tokens from being stolen, even when private keys are accidentally exposed. 
  • ERC-2309 provides a standardized event interface to be emitted during the creation and transfer of one or multiple NFTs.

Notable implementations

Notable implementations of NFTs vary across sectors.

Digital Art

One of the earliest and most well-known applications of NFTs is digital art. NFT platforms like OpenSea and Rarible enable artists to tokenize and sell unique digital artworks.

Gaming

Axie Infinity and CryptoKitties use NFTs for unique in-game assets that players can trade and own.

Music

The Opulus and Audius platforms allow musical artists to sell their music as NFTs. In 2021, Kings of Leon released an album as NFTs and sold it to fans directly.

Virtual real estate

Platforms like The Sandbox and Decentraland allow users to purchase, own, and build on parcels of land within their digital worlds.

Fashion

Brands like RTFKT create NFT fashion items for virtual and real-world use. From December 2021 to 2023, Nike-RTFKT NFTs have earned close to $1.4 billion.

Conclusion

As blockchain technology becomes more integrated into various sectors, NFTs will likely play a key role in reshaping how we think about ownership. 

The scope of NFTs is expected to move well beyond art, and we’ll likely see increased impact in industries like real estate, finance, and intellectual property as mainstream adoption increases.

To further solidify your understanding and get hands-on experience creating NFTs, Cyfrin Updraft has a complete tutorial covering how to develop an NFT collection. 

Related Terms

No items found.