ERC-721 Token

Create Your Own NFT on Ethereum Blockchain

Gaurav Agrawal
Jun 7 · 8 min read

Goal — We will explore the ERC-721 token standard, non-fungibility, and build a SIMPLE ERC-721 token.

What is an ERC 721 Token?

The ERC-721 Token standard is the Ethereum token standard which enables Non-Fungible Tokens (NFTs) on the Ethereum blockchain. Just like the ERC-20 Token standard helps in creating fungible tokens, the ERC-721 focuses on non-fungible asset design. Do you remember CryptoKitties? That is a non-fungible token (i.e. each Kitty asset is unique).

Fungibility

Before understanding non-fungibility, we need to understand what is fungibility. There are two main properties of fungibility:

Interchangeability — You can interchange or replace units of a fungible asset. Your $20 bill and my $20 bill represents the same thing and can be interchanged. Another example that 1kg gold bar is equal to another 1k gold bar. They represent the same value and can be interchanged.

Quantity — You can merge units of a fungible asset to get a higher value in quantity. The example you can add up multiple KGs of grains and now you have the same thing but in a higher quantity.

Nonfungibility

Let’s understand what is non-fungibility.

A non-fungible token represents a unique token which can’t be interchanged with another token.

You can’t divide or add up to get a higher quantity because every unit is unique and you cannot replace or interchange it with other assets. For example, two pieces of land. They are both non-fungible regarding their location.

Image result for cryptokitties
Image source- Every Kitty is unique and represents a different token. Aren’t they cute? See the leftmost one, isn’t he shy? 😋

Example of Non-Fungible Tokens

There are many Non-Fungible Tokens. Some famous NFTs are CryptoKitties, Etheremons, Crypto Bots, and Blockchain Cuties. You can check a full list of ERC-721 tokens here.

The ERC-721 Standard

Now’s let deep dive into the ERC-721 standard. The ERC-721 standard defines a set of methods which help in identifying and interacting with a Non-Fungible Token.

Events- events which get emitted on Transfer, Approvals.

ERC-721 Functions

balanceOf — Will return the balance of an address.

ownerOf — Will return owner address of a token.

safeTransferFrom —Transfer token from one address to another with checks performed to make sure the recipient can accept the token, so it doesn’t get burned/lost.

transferFrom — Transfer token from one address to another address (note: the use of this function is discouraged). The Caller of the method is responsible to put the correct recipient address.

approve — Approve any other address to send a transaction from token owner account to any other account.

setApprovalForAll — Allow or disallow an operator (any address, mostly wallets and exchanges) to send all tokens from owner address to any other address.

getApproved — Return an address which is allowed to transfer token for owners. Return 0 if no address is set.

isApprovedForAll — Return true if given operator (any address) is approved by a given owner.

pragma solidity ^0.4.20;interface ERC721 /* is ERC165 */ {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);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);}

Building an ERC-721 Token using OpenZeppelin and Truffle

Now let’s build an ERC-721 token using the OpenZeppelin library and Truffle.

First, let’s setup Truffle:

mkdir simple
truffle init
npm install openzeppelin-solidity

Let’s create a new Contract for our SIMPLE Token:

pragma solidity ^0.4.24;import "/openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol";
import "/openzeppelin-solidity/contracts/ownership/Ownable.sol";
contract SIMPLEToken is ERC721Full, Ownable{

constructor()
ERC721Full("SIMPLE", "SMPL")
public {}
function mint(address to, uint256 tokenId) public onlyOwner {
_mint(to, tokenId);
}
function _mint(address to) public onlyOwner{
mint(to, totalSupply().add(1));
}
}

Let’s see what we are doing here. We are inheriting two contracts; ERC721FULL and Ownable.

Ownable — Using this contract, we can manage ownership of our contract and are able to mint tokens from the contract owner’s account only.

ERC721FULL — This is a standard implementation of ERC-721 interface we have mentioned above. Let’s see what happens inside this contract:

pragma solidity ^0.4.24;import "./ERC721.sol";
import "./ERC721Enumerable.sol";
import "./ERC721Metadata.sol";
contract ERC721Full is ERC721, ERC721Enumerable, ERC721Metadata {
constructor(string name, string symbol) ERC721Metadata(name, symbol)
public
{
}
}

ERC721FULL internally inherits 3 contracts. We will look mainly into ERC721 to understand the implementation.

Let’s go through the main function one by one. Before that, we will understand, how tokens are getting stored:

// Mapping from token ID to owner
mapping (uint256 => address) private _tokenOwner;
// Mapping from token ID to approved address
mapping (uint256 => address) private _tokenApprovals;
// Mapping from owner to number of owned token
mapping (address => uint256) private _ownedTokensCount;
// Mapping from owner to operator approvals
mapping (address => mapping (address => bool)) private _operatorApprovals;

_tokenOwner — This mapping is needed to store a token against its owner. By using this we can know who is the owner for given tokenId.

_tokenApprovals — This mapping is needed to store tokenId, against an address which is approved by the token owner, to transfer a token on behalf of the owner.

_ownedTokenCount — This mapping is needed to know how many tokens an address owns. If we don’t create this mapping, we have to loop to get this information and looping takes lots of gas on EVM.

_operatorApprovals — Mapping of an owner and an operator (any address, mostly wallets, and exchanges) to check if the owner had given approval or not.

Now let’s see functions in this standard:

balanceOf — This will return the balance of an address. First, it checks for a valid address and then using _ownedTokensCount returns the count of the token.

function balanceOf(address owner) public view returns (uint256) {
require(owner != address(0));
return _ownedTokensCount[owner];
}

OwnerOf — This will return the owner address for a given token using _tokenOwner mapping.

function ownerOf(uint256 tokenId) public view returns (address) {
address owner = _tokenOwner[tokenId];
require(owner != address(0));
return owner;
}

approve — This will approve an address to transfer a token on behalf of the owner. The function first checks if the owner called the function or if the call is approved by the owner to send all tokens. Then it updates the _tokenApprovals mapping if everything is correct.

function approve(address to, uint256 tokenId) public {
address owner = ownerOf(tokenId);
require(to != owner);
require(msg.sender == owner || isApprovedForAll(owner, msg.sender));
_tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}

safeTransferFrom — There are two functions with similar names, but with different arguments. These functions internally call transferFrom function. Though they also perform one more important duty. They check if the recipient address is valid for receiving the token or not. This helps in the security of the token.

function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes _data
)
public
{
transferFrom(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, _data));
}

transferFrom — This is the main function to transfer a token from one address to another address. Let’s see what is it doing:

1- Check if the token is either owned by called or approved to the caller. Also, checks of a valid address.

2- Clear approval, remove current ownership and reduce the token count of the current owner.

3- Add token to recipient account and increase the token count for the recipient.

function transferFrom(
address from,
address to,
uint256 tokenId
)
public
{
require(_isApprovedOrOwner(msg.sender, tokenId));
require(to != address(0));
_clearApproval(from, tokenId);
_removeTokenFrom(from, tokenId);
_addTokenTo(to, tokenId);
emit Transfer(from, to, tokenId);
}

setApprovalForAll — This function approves the address to transfer all tokens on behalf of the owner. It first checks if the called and to address are not the same, and then updates the _operatorApprovals mapping.

function setApprovalForAll(address to, bool approved) public {
require(to != msg.sender);
_operatorApprovals[msg.sender][to] = approved;
emit ApprovalForAll(msg.sender, to, approved);
}

isApprovedForAll — This function checks if the owner approved the operator to transfer tokens or not.

function isApprovedForAll(
address owner,
address operator
)
public
view
returns (bool)
{
return _operatorApprovals[owner][operator];
}

getApproved — Returns the approved address for given tokenId.

function getApproved(uint256 tokenId) public view returns (address) {
require(_exists(tokenId));
return _tokenApprovals[tokenId];
}

You can check the other helper functions and inherited contracts.

If you don’t understand something, please ask in comments! We’re here to help!

Testing ERC-721 Token

Now we will test our SIMPLE token and play around it. We will use a local built-in blockchain to deploy and test the contract.

truffle develop

This will give us the Truffle console.

Now let’s deploy our contract. Remember! You need to add a migration file. You can check the attached GitHub repo at the end of the article.

truffle compile
migrate --reset
SIMPLEToken.deployed().then((simple) => {token = simple;})

Now, let’s mint some tokens, and perform a test transfer & approval.

token._mint(web3.eth.accounts[0]) // will mint a new tokentoken.totalSupply() // check token's total supplytoken.safeTransferFrom(web3.eth.accounts[0] , web3.eth.accounts[1], 1) // transfer token (token id 1) from 0'th account to 1st accounttoken.ownerOf(1) // check owner of token id 1token._mint(web3.eth.accounts[0]) // will mint another tokentoken.approve(web3.eth.accounts[3] , 2) // approve token id 2 to  account[3]token.safeTransferFrom(web3.eth.accounts[0] , web3.eth.accounts[1], 2 , {from:web3.eth.accounts[3]})   // Note that we are adding {from:web3.eth.accounts[3]}, this mean that we are invoking this function using account[3]

You can go ahead and test all methods (OpenZeppelin is a well-tested library so you don’t need to worry about the functionality provided by the library). You should instead be focusing on testing any functionality added by you 😎

Conclusion

Today we have created a Non-Fungible Token using Solidity on Ethereum. NFT tokens are mostly used as collectibles and have a variety of other use-cases, such as representing real-estate, artwork, certificates, loans, etc… There is a big use-case in gaming where one can use NFTs to represent items and collectibles across games and platforms. This is really exciting!

What’s Next?

In the next article, we will convert this into an upgradable contract using ZeppelinOS… So subscribe & stay tuned!

Let us know what you want to learn about in the comment section.👇

About QuikNode

QuikNode is building infrastructure to support the future of Web3. Since 2017, we’ve worked with hundreds of developers & companies, helping scale dApps and providing high-performance Ethereum nodes. We’re working on something interesting from the past few months and will be launching soon, so subscribe our newsletter for more updates!! 😃

QuikNode

Cloud-hosted Ethereum nodes for dApps, developers, and power-users. Run a dedicated ETH node in minutes!

Gaurav Agrawal

Written by

Coincodecap.com & Coinmonks publication (https://medium.com/coinmonks) and Editor — Coinmonks, Crowdbotics, and QuikNode

QuikNode

QuikNode

Cloud-hosted Ethereum nodes for dApps, developers, and power-users. Run a dedicated ETH node in minutes!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade