ERC 721 The NFT Standard
Tags: nft, solidity, erc721
Published: 2022-01-21 15:38:49

**ERC 721** represent the standard for the minting and transfer of Non Fungible Token otherwise known as **NFT**. A non fungible token is a token that is unique and different from another. It cannot be broken down into smaller pieces unlike the ERC 20 tokens that are divisible to smaller pieces.

NFT are revolutionary right now and they represent the future. An NFT could represent anything from a media file, to a digital art, certificate, title deeds or a lease agreement. The use of NFT is enormous and knowing how they work is the goal of this blog post.

## ERC 721 Standard
The ERC 721 is the standard that defines the methods, events and state variables that a non fungible token (**NFT**) should posses to be compliant. 

### Interface of ERC 721
The **ERC 721** standard for NFT is more complicated than the one of **ERC 20**. It spans several interface and contracts with some marked as core while others are optional. Below is the OpenZepplin implementation of the contract interface.
```solidity
 // SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

interface IERC721 is IERC165 {

    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);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function setApprovalForAll(address operator, bool _approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;
}

```
## Interface of ERC 165
The ERC 721 interface inherits from ERC 165. The ERC 165 is a contract that is used for **standard interface detection**. It allows the client/caller to check if a function is available for call in the target contract.

The interface IERC 165 which ERC 165 inherits from defines the following function:

```solidity
function supportsInterface(bytes4 interfaceId) external view returns (bool)
```
## Variables and state in ERC 721 standard
The following are the state variables defined in the standards implementation by OpenZepplin.

### Token owner mapping
This is a mapping of `uint256` to an `address`. It is used to find the owner of a given **NFT ID**

```solidity
  mapping(uint256 => address) private _tokenOwner;
  function ownerOf(uint256 tokenId) public view returns (address){
    address owner = _tokenOwner[tokenId];
    require(owner != address(0);
    return owner;
}
```
The `_tokenOwner` mapping is private preventing direct access. To get value from the `_tokenOwner` mapping, the function `ownerOf` which accepts a uint256 as a tokenId is used to find the address associated with an NFT.

### Approved address mapping
The owner of a NFT can give approval to another address. Once the approval is given, that address will be able to transfer the NFT from the owner to themselves or another address. This approvals is managed by a mapping called `_tokenApprovals`.

```solidity
 mapping(uint256 => address) private _tokenApprovals;
 function getApproved(uint256 tokenId) public view returns (address) {
   require(_exists(tokenId));
   return _tokenApprovals[tokenId];
}
```
The mappings `_tokenApprovals` is used to store the approval given by a token owner to an address. This approval allows that address to transfer the token on behalf of the owner. This mapping is private and the function `getApproved` is used to pull out the address that is allowed to transfer a particular NFT on behalf of an owner. The `getApproved` function uses an internal function `_exists` to check if the provided tokenid is a valid NFT ID on the smart contract.

### Number of token owned per owner
This state variable is used to track and store the number of token owned by an address in a contract. It consist of a mapping with an address as the key and uint256 as the number of tokens owned.

```solidity
  mapping(address => uint256) private _ownedTokensCount;
  function balanceOf(address _owner) public view returns (uint){
     require(_owner != address(0));
     return _ownedTokensCount[_owner];
  }
```

### Operator approval
An **operator** is a third party that is trusted and can be approved by an owner to have access to all the NFTs owned by them. If an owner gives permission to an **operator**, that operator is allowed to withdraw any NFT from that owner's address and they are allowed to transfer it to themselves or others.

The operator approval is stored in a nested mapping data structure.

> The **operator** approval is different from the **tokenApproval**. The operator approval grants the right to an operator to be able to withdrawal potentially all the NFTs from an owner's address while in the tokenApproval, the owner gives an address permission to be able to withdraw just a particular NFT.

```solidity
  mapping(address => mapping(address => bool)) private _operatorApproval;
function setApprovalForAll(address to, bool approved) public {
   require(to != msg.sender);
   _operatorApproval[msg.sender][to] = approved;
   emit ApprovalForAll(msg.sender, to, approved);
}

function isApprovedForAll(address owner, address operator) public view returns (bool) {
  return _operatorApproval[owner][operator];
}
```
The function `setApprovalForAll` is used to grant approval to an address (operator like **Open Sea**) access to manage your NFTs. The function checks and ensure that the `to` address is not the same as `msg.sender`. Then it proceeds to save the approval on the state variable `_operatorApproval`. The owner of the NFTs is the caller of this function.

The next function `isApprovedForAll` checks if an operator has approval to an `owner's` NFTs. It returns the value stored in the state variable `_operatorApproval`.

## ERC 721 standard functions
The standard contains some useful functions for the management and transfers of NFTs. Below is the implementation of some of this functions by [OpenZepplin](https://openzeppelin.com/)

### Constructor

```solidity
  constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }
```
OpenZepplin implementation of ERC 721 standard also inherits from **IERC721Metadata** interface that stipulates that an NFT contract should have a name and a symbol. The constructor accepts two string parameters that are used to set the name and symbol of the NFTs contarct.

### balanceOf

This is used to check how many NFTs does someone own. 

### ownerOf
This is used to check the owner of a particular NFT.
```solidity
  function ownerOf(uint256 tokenId) public view returns (address) {
     address owner = _owners[tokenId];
     require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }
``` 

### approve
The `approve` function is used to grant approval by the NFT owner of a particular NFT token to an address for the purpose of helping to administer that NFT. The function checks firstly, if the `msg.sender` is the token owner or has approval to the token. if all checks clears, the `_tokenApprovals` state variable is updated with the `tokenId` pointing to the `to` address. ( this means the to address can manage that token )

```solidity
   function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");
        require(msg.sender == owner || isApprovedForAll(owner, 
        msg.sender),
            "ERC721: approve caller is not owner nor approved for 
        all");
        _tokenApprovals[tokenId] = to;
        emit Approval (owner, to, tokenId);
    }
```

### getApproved function

This function implementation is defined above. It is used to return the address that is approved to manage an NFT.

### setApproval for all
This function is used to grant an operator full access to all the NFTs available in an owner's address. 

### isApprovedForAll

This function returns a boolean showing if an operator has access or not to an owner NFts. 

### transferFrom

This is a public function used to transfer the NFT from the owner's account to the receiver's account. For this function to work, approval must have been given prior by the owner of the NFT to an address for the purpose of managing the NFT. 

**The function accepts three parameters which are :**

from : address of the NFT owner
to: the address of the receiver
tokenId: the ID of the NFT to transfer.

Before the `transferFrom` function is called, the NFT owner must first approve that `msg.sender` calling the function has the approval to transfer that NFT. This is done by the owner by calling the `approve` function.

**Implementation of transferFrom function**

```solidity
  function transferFrom(address from, address to, uint256 tokenId) public {
  require(_isApprovedOrOwner(msg.sender, tokenId);
  _transfer(from, to, tokenId);
}

function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }
```
The function checks if the `msg.sender` is the owner of the NFT, or has approval to manage the NFT. If it does the transfer is executed by the internal function `_transfer`.

### safeTransferFrom

This is a public function that is used to safely transfer the NFT from the owner's account to the recipient account. Safely means that, if  the recipient is a contract, you will need to ensure, that the contract has callbacks registered to let it know when a NFT was successfully transferred to it.

**The function takes the following parameters :**

from: the address of the current owner of the NFT
to: the recipient of the NFT
tokenId: the ID of the NFT to be transferred
_data : the bytes data that is forwarded if `to` is a contract.

**Implementation of safeTransferFrom**

```solidity
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
   _transfer(from, to, tokenId);
   require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
```
The `safeTransferFrom` function has two overloads. One overload function is used to transfer NFT to an **EOA** address (externally owned account ), that is an account address in our wallet. This overload for transferring to an EOA receives only three (3) parameters. The last **_byte** parameter is missing.

The other overloaded of the `safeTransferFrom` function is used to transfer NFT to a contract address. For a contract to be able to receive NFT, it must implement the `IERC721Receiver` interface. This interface specifies that a contract that wants to accept NFTs must implement the function named `onERC721Received`. 

```solidity
   function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
```

### _mint internal function

The `_mint` internal function is used to mint a new NFT at the given address. The function takes two arguments which are:

to: the address of the owner for which the NFT is minted
tokenId: the new unique tokenId for the token to be minted

When the `_mint` function is called a Transfer event is emitted.

### _burn internal function

This internal function is used to destroy or burn a given NFT from the owner's address. It accepts two parameters which are:

owner : the address of the owner whose token is to be burned
tokenId : the tokenId of the NFT to be burned

## Summary

The **ERC 721** standard has a lot of functions, events that is necessary for the transfer of NFTs. I just covered a bit of them here to prevent this blog post for becoming too long. In my next blog post, I will show how to create a contract to mint NFTs.

Until then, thanks for reading.

Happy coding!