The non-fungible tokens, or in short NFTs, have one the most influential roles in the crypto and Web3 scene. These are digital assets that can represent digital art, virtual collectibles, assets in games, and more.
Ethereum Foundation introduced the ERC-721 standard in 2017, which helped all the crypto wallets, brokers, and protocols use this feature in a translatable way between any crypto solution.
In this post, we will look more deeply into the ERC-721 standard and explore how it is used in the Solidity programming language for the Ethereum blockchain.
What Is an NFT?
Non-fungible tokens represent unique assets that can't be exchanged, copied, substituted, or divided. Each token has a unique identifier. Therefore, we can see who owns what.
An NFT can represent but is not limited to:
- digital work of art;
- virtual collectibles;
- digital assets in games;
- ownership rights, for instance, to a real estate;
- tickets that have unique data like seating place;
- a negative loan like a mortgage.
NFTs are like humans - there are no two people the same.
The ERC-721?
To represent an NFT, the Ethereum Foundation has implemented it with an ERC-721 token standard. This standard describes a table of who owns what. The standardized approach helps crypto wallets, brokers, and auctions to work with NFTs on Ethereum and other EVM blockchains.
The ERC-721 standard was launched in 2017 and authored by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs. I met William Entriken at one of the NFT conferences.
Functions
There are several functions defined in the ERC-721 standard. Let's look at the most important ones separately.
Ownership Functions
These functions describe ownership rights.
balanceOf(address _owner)
Ā returns a number of NFTS owned byĀ_owner
Ā address;
ownerOf(uint256 _tokenId)
Ā returns an owner who owns a token with theĀ_tokenId
Ā identifier;
Transfer Functions
These functions help to transfer a token.
safeTransferFrom(address _from, address _to, uint256 _tokenId)
Ā transfers the token with theĀtokenId
Ā identifier from theĀ_from
Ā address to theĀ_to
Ā address checking that recipient is aware of the ERC-721 standard;
safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data)
Ā similar to the function above with the option to sendĀdata
Ā to the toĀ_to
Ā in a call, can be useful to send additional data;
approve(address _approved, uint256 _tokenId)
Ā gives rights to theĀ_approved
Ā to transfer the token with theĀ_tokenId
Ā identifier;
getApproved(uint256 _tokenId)
Ā returns the wallet address approved to transfer the token withĀ_tokenId
Ā identifier;
setApprovalForAll(address _operator, bool _approved)
Ā approve or remove approval for all theĀmsg.sender
Ā assets toĀ_operator
;
isApprovedForAll(address _owner, address _operator)
Ā checks if theĀ_operator
Ā address has rights for theĀ_owner
Ā assets.
Events
Events help to notify about changes with an NFT. For instance, the web frontend can be updated.
Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId)
Ā emits when the token with theĀ _tokenId
Ā identifier is transferred from theĀ _from
Ā to theĀ _to
.
Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId)
Ā emits when approval rights are given to theĀ _approved
Ā to the token with theĀ _tokenId' identifier by the
_owner` who has ownership rights;
ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)
Ā emits whenĀ _owner
Ā gives or removes rights to theĀ _operator
Ā for their assets.
Metadata Extension
A commonly used ERC-721 extension describes metadata in a JSON format. It represents information about a token, like a name, symbol, and token URI. Using this extension, crypto wallets can ask for the information using three functions:
function name() external view returns (string _name)
Ā returns a descriptive name;
function symbol() external view returns (string _symbol)
Ā returns token symbol;
function tokenURI(uint256 _tokenId) external view returns (string)
Ā returns URL to JSON file that describes the NFT. Usually, this is stored on IPFS.
The token URI points to a JSON file that conforms to the ERC721 Metadata JSON Schema:
{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image representing the asset to which this NFT represents. "
}
}
}
If you're unfamiliar with IPFS file storage, we will discuss that in one of the following posts.
Putting It All Together
The most straightforward way to implement NFTs is to use the OpenZepplin contracts suite that includes theĀ ERC721
Ā andĀ ERC721URIStorage
Ā contracts. It fully conforms to the ERC-721 and metadata extension standards.
Let's build an NFT ticketing smart contract.
First, we must import OpenZepplin ERC-721 contract implementations and conform our smart contract to it.
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract Ticket is ERC721URIStorage {
/// ...
}
At first, we need to define the NFT name and symbol. To do that, we pass the event name and description to smart contract the constructor.
constructor(
string memory eventName,
string memory shortName
) ERC721(eventName, shortName) {
// initializes the NFT storage
}
We can now start minting NFTs because the OpenZepplin hides away all the nitty gritty implementation details.
function createTicket(address visitor, string memory tokenURI) external {
tokenId++;
// mint the NFT and assign to the visitor address
_mint(visitor, tokenId);
// set the token JSON file link that was uploaded to the IPFS
_setTokenURI(tokenId, tokenURI);
console.log(tokenId);
}
All the ERC-721 functions discussed above comeĀ for freeĀ with the OpenZepplin contracts.
TL;DR
The non-fungible tokens, or NFTs, differ much from regular ERC-20 tokens. They describe uniqueness. Remember what we discussed at the beginning of this post - NFTs are like humans. Or let me call it differently this time - NFTs are like kitties - there are no two kitties alike.
The most commonly used standard that defines NFTs is ERC-721, with metadata extension. It helps to attach metadata to the token, like images which is the most used feature in the crypto world.
Links
- Sample code
- EIP-721: Non-Fungible Token Standard
- What is ERC-721? The Ethereum NFT Token Standard
- How to create and deploy an ERC-721 (NFT)
- ERC721 by OpenZepplin