The Anatomy of ERC721

Understanding Non-Fungible Ethereum Tokens

Crosspost: This post was originally written and published by Gerald Nash, here. It was reposted to BlockChannel with his permission.

Many have heard of the new game on the Ethereum blockchain called CryptoKitties. The new game recently made several headlines within the cryptocurrency community because of its extremely unique idea and the dent it’s made on the Ethereum network. CryptoKitties is a game in which players can buy, sell, trade, and breed digital cats. They can be thought of as “breedable Beanie Babies” in that each cat is unique in some way. This uniqueness makes the CryptoKitties extremely collectible, as someone could take interest in the characteristics of several kittens and wish to own many of them.

Source: Vice Media

But collectibles aren’t limited to digital felines. Humans have always had a history of collecting things; it’s nothing new. From physical coins to Pokémon cards, people love collecting. It’s a hobby that forms as a result of a unique interest in scarce items. Similar to how the value of a commodity is related to its scarcity, the value of a collectible item to a collector is connected to its rareness among other items.

We can emulate rare, collectible items with Ethereum tokens, and each of these tokens follows a novel standard in the Ethereum community known as ERC721. Ethereum Request for Comments 721, or ERC721, is an Ethereum Improvement Proposal introduced by Dieter Shirley in late 2017. It’s a proposed* standard that would allow smart contracts to operate as tradeable tokens similar to ERC20. ERC721 tokens are unique in that the tokens arenon-fungible.


Fungible — being something (such as money or a commodity) of such a nature that one part or quantity may be replaced by another equal part or quantity in paying a debt or settling an account. Source: Merriam-Webster


Fungibility is, essentially, a characteristic of an asset, or token in this case, that determines whether items or quantities of the same or similar type can be completely interchangeable during exchange or utility. For example, the US Five Dollar bill below can be used to purchase a soda from a convenience store.

It has value and can be used to purchase items with the same or less value. However, when one goes to purchase a soda with the following baseball card, the store owner won’t accept it.

Source: Baseball Card Stars

Why wouldn’t the above Carlos Santana card be accepted by the store owner when it’s worth $5, same as the above bill? This is because the baseball card and the dollar bill have differing characteristics that define their value to certain people. A 7 year old child could be willing to pay $7 for the baseball card, because they like the colors on the card. But, the store owner doesn’t even value the card at $5 simply because it wasn’t issued by the Federal Reserve like the dollar is. The unique attributes that the baseball card and dollar bill have are what take away their fungibility, as they are valued differently depending on the exchange and cannot always be used interchangeably.

In terms of collectible items, two items in a collection are not fungible if they have different characteristics. For physical coins, a gold coin is not fungible with a copper coin, because their differing characteristics give them different values to collectors.

ERC721 tokens can be used in any exchange, but their value is a result of the uniqueness and rareness associated with each token. The standard defines the functions name , symbol , totalSupply , balanceOf , ownerOf , approve , takeOwnership , transfer , tokenOfOwnerByIndex , and tokenMetadata . It also defines two events: Transfer and Approval .

Note: This is a concise declaration of an example ERC721 contract.

An overview of each field within the contract is as follows.

KEEP IN MIND: The following code is solely for educational purposes and is not tested. Please do not implement it in production applications!

ERC20-like Functions

ERC721 defines a few functions that give it some compliance with the ERC20 token standard. It does this to make it easier for existing wallets to display simple information about the token. These functions let the smart contract that fits this standard act like a common cryptocurrency such as Bitcoin or Ethereum by defining functions that let users perform actions such as sending tokens to others and checking balances of accounts.

name

This function is used to tell outside contracts and applications the name of this token. An example implementation of the function can be as follows.

contract MyNFT {
function name() constant returns (string name){
return "My Non-Fungible Token";
}
}

symbol

This function also helps in providing compatibility with the ERC20 token standard. It provides outside programs with the token’s shorthand name, or symbol.

contract MyNFT {
function symbol() constant returns (string symbol){
return "MNFT";
}
}

totalSupply

This function returns the total number of coins available on the blockchain. The supply does not have to be constant.

contract MyNFT {
// This can be an arbitrary number
uint256 private totalSupply = 1000000000;
function totalSupply() constant returns (uint256 supply){
return totalSupply;
}
}

balanceOf

This function is used to find the number of tokens that a given address owns.

contract MyNFT {
mapping(address => uint) private balances;
function balanceOf(address _owner) constant returns (uint balance)
{
return balances[_owner];
}
}

Ownership Functions

These functions define how the contract will handle token ownership and how ownership can be transferred. The most notable of these functions aretakeOwnership and transfer , which act like withdraw and send functions, respectively, and are essential for letting users transfer tokens between each other.

ownerOf

This function returns the address of the owner of a token. Because each ERC721 token is non-fungible and, therefore, unique, it’s referenced on the blockchain via a unique ID. We can determine the owner of a token using its ID.

contract MyNFT {
mapping(uint256 => address) private tokenOwners;
mapping(uint256 => bool) private tokenExists;
function ownerOf(uint256 _tokenId)
constant returns (address owner) {
require(tokenExists[_tokenId]);
return tokenOwners[_tokenId];
}
}

approve

This function approves, or grants, another entity permission to transfer a token on the owner’s behalf. For example, if Alice owns 1 MyNFT she can call the approve function for her friend Bob. After a successful call, Bob could take ownership of or perform operations on the token at a later time on Alice’s behalf. More information on transferring ownership can be seen in the takeOwnership and transfer functions.

contract MyNFT {
mapping(address => mapping (address => uint256)) allowed;
function approve(address _to, uint256 _tokenId){
require(msg.sender == ownerOf(_tokenId));
require(msg.sender != _to);
allowed[msg.sender][_to] = _tokenId;
Approval(msg.sender, _to, _tokenId);
}
}

takeOwnership

This function acts like a withdraw function, since an outside party can call it in order to take tokens out of another user’s account. Therefore, takeOwnershipcan be used to when a user has been approved to own a certain amount of tokens and wishes to withdraw said tokens from another user’s balance.

contract MyNFT {
function takeOwnership(uint256 _tokenId){
require(tokenExists[_tokenId]);
address oldOwner = ownerOf(_tokenId);
address newOwner = msg.sender;
require(newOwner != oldOwner);
require(allowed[oldOwner][newOwner] == _tokenId);
balances[oldOwner] -= 1;
tokenOwners[_tokenId] = newOwner;
balances[oldOwner] += 1;
Transfer(oldOwner, newOwner, _tokenId);
}
}

transfer

The next method of transferring tokens is using this function. transfer lets the owner of a token send it to another user, similar to a standalone cryptocurrency. However, a transfer can only be initiated if the receiving account has previously been approved to own the token by the sending account.

contract MyNFT {
mapping(address => mapping(uint256 => uint256)) private ownerTokens;
function removeFromTokenList(address owner, uint256 _tokenId) private {
for(uint256 i = 0;ownerTokens[owner][i] != _tokenId;i++){
ownerTokens[owner][i] = 0;
}
}
function transfer(address _to, uint256 _tokenId){
address currentOwner = msg.sender;
address newOwner = _to;
require(tokenExists[_tokenId]);
require(currentOwner == ownerOf(_tokenId));
require(currentOwner != newOwner);
require(newOwner != address(0));
removeFromTokenList(_tokenId);
balances[oldOwner] -= 1;
tokenOwners[_tokenId] = newOwner;
balances[newOwner] += 1;
Transfer(oldOwner, newOwner, _tokenId);
}
}

tokenOfOwnerByIndex (Optional — Recommended)

Each non-fungible token owner can own more than one token at one time. Because each token is referenced by its unique ID, however, it can get difficult to keep track of the individual tokens that a user may own. To do this, the contract keeps a record of the IDs of each token that each user owns. Because of this, each individual token owned by a user can be retrieved by its index in the list (array) of tokens owned by the user. tokenOfOwnerByIndex lets us retrieve a token in this method.

contract MyNFT {
mapping(address => mapping(uint256 => uint256)) private ownerTokens;
function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId){
return ownerTokens[_owner][_index];
}
}

Metadata Function

Like we’ve said before, what makes non-fungible items non-fungible is their unique set of attributes. A dollar and a baseball card are not fungible, because they have different characteristics. But, storing data on the blockchain that tell the defining characteristics of each token is extremely expensive and not recommended. To combat this, we can store references, like an IPFS hash or HTTP(S) link, to each token’s attributes on the chain so that a program outside of the chain can execute logic to find more information about the token. These references are data about data, or metadata.

tokenMetadata (Optional — Recommended)

This function lets us discover a token’s metadata, or the link to the its data.

contract MyNFT {
mapping(uint256 => string) tokenLinks;
function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl) {
return tokenLinks[_tokenId];
}
}

Events

Events are fired whenever a contract calls them, and they’re broadcasted to any listening programs once they’ve been fired. Outside programs listen to blockchain events so that they can execute logic once the event is fired using the information that the event provides. The ERC721 standard defines two events that are as follow.

Transfer

This event is fired whenever a token changes hands. It’s broadcasted when a token’s ownership moves from one user to another. It details which account sent the token, which account received the token, and which token (by ID) was transferred.

contract MyNFT {
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
}

Approval

This event is fired whenever a user approves another user to take ownership of a token (i.e. whenever approve is executed). It details which account currently owns the token, which account is allowed to own the token in the future, and which token (by ID) is approved to have its ownership transferred.

contract MyNFT {
event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
}

The source code of my implementation of the ERC721 standard can be found here.

Similar to ERC20, the newly proposed ERC721 standard has opened up a gateway for new smart contracts to act as non-fungible items. As can be seen in applications like CryptoKitties, Decentraland, CryptoPunks, and many others, non-fungible tokens have proven to be very high demand products. Even WikiLeaks owns several high value CryptoKitties! But, ultimately this standard will further expand the cryptocurrency economy and help advance the field even more.

At the time of this writing, ERC721 is not an official standard and is still undergoing revisions.
Like what you read? Give Steven McKie a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.