ERC #721

Non-fungible Token Standard

Presentation by @protolambda

What is an "ERC" to begin with?

RFC: Request For Comments

ERC: Ethereum Request for Comments

Memos in the Requests for Comments (RFC) document series contain technical and organizational notes about the Internet.

Origin of ERC:

When will the first April Fools ERC be?

Complex Addressing in IPv6:


ERC #721

  • Standard for Non Fungible Tokens (NFTs)
    • Aka: colored coins
  • Geared to look somewhat like ERC #20
    • Compatibility
  • Interface (method signatures + events) definitions for NFT smartcontracts.
    • Implementations can be very different, but the user doesn't have to deal with it.
  • Created on 20 Sept 2017. Before the cryptokitties hype took off.


  • Standardization, useful as guide for compatibility between platforms/wallets/exchanges/etc.
  • Adoption, ERC#20 did a lot for the ecosystem
  • Avoid re-deploying a new contract for each "colored coin". (Older tech just deployed a new contract for each asset; not feasibly for e.g. CryptoKitties.)






ERC #721

  • Non-fungible (i.e. no fractions, no mixing)
  • IDs for each coin
  • Properties can differ per coin: e.g. cryptokitty DNA

ERC #20

  • Fungible, 1.42 ZRX is perfectly fine.
  • No IDs, only amounts
  • My 1.23 ABC is no different than yours.

Recap of NFT differences

Function signatures

symbol() constant returns (string symbol)


May be less "meaningful" in #721 implementations.

name() constant returns (string name)

totalSupply() constant returns (uint256 totalSupply)

function balanceOf(address _owner) constant returns (uint256 balance)



The common ones:

Function signatures (pt. 2)

The similar ones:

function transfer(address _to, uint256 _tokenId)

function transfer(address _to, uint256 _value) returns (bool success)
function transferFrom(address _from, address _to, uint256 _value)
 returns (bool success)
function takeOwnership(uint256 _tokenId)

ERC #721:

ERC #20:

(The similar ones)

function approve(address _to, uint256 _value) returns (bool success)

ERC #721:

ERC #20:

function allowance(address _owner, address _spender) constant returns (uint256 remaining)
function approve(address _to, uint256 _tokenId)

Side note:

both approval functions overwrite old approvals

ERC #721 implicit allowance function, not in spec:

mapping(uint256 => address) public approved;

Function signatures (pt. 3)

function ownerOf(uint256 _tokenId) constant returns (address owner)

Unique for ERC #721:

Priority flaw? One directional lookup:

tokenID > owner

Contrasts with general idea of wallets:

owner > tokenID list 


function tokenOfOwnerByIndex(address _owner, uint256 _index)

constant returns (uint tokenId)

"Optional" function : reading the actual wallet from user-space.
Not necessary if the user already knows the ID values of their tokens.


Optional work-around : a 2nd mapping, redundant, but more efficient lookup. Still a little bit extra gas, but better than for loops.


Ignore option: query centralized, and validate decentralized with "ownerOf(...)"

(Unique for ERC #721)

function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl)

Optional in a good sense; IPFS/links/etc. are not "core"

(Unique for ERC #721)

event Transfer(address indexed _from, address indexed _to,      
 uint256 _value)


ERC #20:

event Approval(address indexed _owner, address indexed _to,    uint256 _value)

ERC #721:

event Approval(address indexed _owner, address indexed _to,    uint256 _tokenId)
event Transfer(address indexed _from, address indexed _to,    
 uint256 _tokenId)
//get event handle, with filter (optional)
var myEvent = myContract.myEvent({id: 1});

//watch for new events, result) => {
    var data = result["args"]["data"];
    var id = result["args"]["id"]; // always 1 (filtered)
    // Do Something 

Web3js Events

Pull Request #15832:

Events in go-ethereum!

Merged 2 days ago!

Geth Events

filterer := myContract.MyContractFilterer

//create new channel, where Geth can send its events to
watcher := make(chan *abi.PepeBasePepeBorn)

//watch for myEvent (args: Filter options, output channel, filter properties)
subscription, err := filterer.WatchMyEvent(nil, watcher, 1)
if err != nil {

// Note: range over channel data (continuous stream)
for myEvent := range watcher {
    fooBar := myEvent.MyFooBar
    // Do something

Some links for the reader:

ERC #721 thread

ERC #20 thread


(and their bottlenecks...)

Source: technical details page (see below)

  • November 28: launch
  • December 2: Genesis kitty sold


  • 480k cats, 6.6k gen0
  • Birthing fees (rougly): 0.002*100 + 0.015*373 = 5.8k Ether
    • Note: includes Tx fees, which aren't revenue
  • Gen 0 sold (very rough, ignoring special cases)(6,500-800)*0.1= 570 Ether

Main problems

CryptoKitties tackled:


  • Auctions, with efficient price changing; i.e. no gas costs.
    • Solution: Descending Clock Auctions
  • Wallet/market explorer, with efficiency.
    • Centralized (Google Cloud) query + file service
    • Decentralized verification of wallet contents, using web3js


= Standardized metadata

Multi-address to IPFS or HTTPS data-bundle:

# Human readable multi-address example:

Bundle standard:

  • `name` (required, UTF-8)
  • `image` (opt., PNG/JPEG/etc.)
  • `description` (opt., UTF-8)
  • Optionally more sub-paths
  • Each sub-path has a `default` sub-path, e.g. `/image/default` with a default image.
  • Language code support, e.g.: `/name/fr`

Overview Token-ERCs




ERC 721

By protoslides

ERC 721

Presentation about ERC #721

  • 411
Loading comments...

More from protoslides