EIP: <not assigned>
Title: Distinguishable Assets Registry
Author: Esteban Ordano <[email protected]>
Type: Standard Track
Category: ERC
Status: Draft
Created: 2018-01-05
Updated: 2018-02-21
Summary
A Distinguishable Assets Registry (DAR for short) is a contract that tracks ownership of, and information about a set of assets that are distinguishable from each other, and are commonly refferred to as "NFTs", for "Non Fungible Tokens".
See https://github.com/decentraland/erc821 for a reference implementation.
See the "Revisions" section for a history of this ERC.
Abstract
Tracking the ownership of physical or digital distinguishable items on a blockchain has a broad range of applications, from virtual collectibles to physical art pieces. This proposal aims at standardizing a way to reference distinguishable assets along with the foreseeable required operations and interfaces for effective management of those assets on a blockchain.
Introduction
The number of virtual collectibles tracked on the Ethereum blockchain is rapidly growing, creating a demand for a more robust standard for distinguishable digital assets. This proposal suggests improvements to the vocabulary used to refer to such assets, and attempts to provide a solid and future-proof reference implementation of the basic functionality needed. This EIP also proposes better naming conventions and vocabulary for the different components of the NFT economy: the assets, the NFTs (representations of those assets on the blockchain), the DARs (the contracts registering such assets), distinguishing ownership from holding addresses, and more.
See also: ERC #721, ERC #20, ERC #223, and ERC #777.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Specification
A non-fungible token (NFT) is a distinguishable asset that has a unique representation as a register in a smart contract. This proposal specifies such contracts, referenced as Distinguishable Asset Registries, or DARs. NFTs MAY also be reffered to as "deeds".
DARs can be identified by the blockchain in which they were deployed, and the 160-bit address of the contract instance. NFTs are identified by an ID, a 256 bit number, which MAY correspond to some cryptographic hash of the non-fungible's natural key.
NFTs SHOULD be referenced by a URI that follows this schema:
nft://<chain's common name>/<DAR's address>/<NFT's ID>
The NFT's ID SHOULD have a hint that helps to decode it. For instance, if the encoding of the number is in hexadecimal, the NFT’s ID SHOULD start with 0x. The DAR's address SHOULD follow the checksum by casing as proposed on #55.
Some common names for Ethereum blockchains are:
ethereum, livenet, or mainnet
ropsten, testnet
kovan
rinkeby
Some examples of NFT URIs follow:
- nft://ethereum/0xF87E31492Faf9A91B02Ee0dEAAd50d51d56D5d4d/0
- nft://ropsten/0xF87E31492Faf9A91B02Ee0dEAAd50d51d56D5d4d/0xfaa5be24e996feadf4c96b905af2c77c456e2debd075bab4d8fd5f70f209de44
Every NFT MUST have a owner address associated with it. NFTs associated with the null address are assumed non-existent or destroyed.
An owner MAY assign one or multiple operator addresses. These addresses will be able to transfer any asset of the owner.
DARs MUST trigger Transfer events every time a NFT's owner changes. This might happen under three circumstances:
- A NFT is created. In this case, the
from value of the Transfer event MUST be the zero address.
- A NFT is transferred to a different owner.
- A NFT is destroyed. In this case, the
to value of the Transfer event MUST be the zero address.
Transfer events SHALL NOT simultaneously have a zero value in both the from and to fields (this means, the same Transfer event can't both create and destroy a token).
Associated Metadata
Metadata associated with each asset is out of scope for this standard.
DAR global methods
totalSupply():uint256
Return the total amount of assets under this DAR. This method SHALL NOT throw.
supportsInterface(bytes8 interfaceId):bool
This method returns true if the interfaceId is a supported interface (165, corresponding to 0x01ffc9a7, or 821, corresponding to 0x959d7abb). This method SHALL NOT throw.
NFT getter methods
exists(uint256 assetId):bool
This method returns a boolean value, true if the asset identified with the given assetId exists under this DAR. This method SHALL NOT throw.
ownerOf(uint256 assetId):address
This method returns the address of the owner of the NFT. This method SHALL NOT throw. If the assetId does not exist, the return value MUST be the null address.
Owner-centric getter methods
balanceOf(address owner):uint256
This method returns the amount of NFTs held by the owner address under this DAR. This method MUST not throw.
assetByIndex(address owner, uint256 index):uint256
This method returns the ID of the indexth NFT held by the owner address under this DAR, when all the IDs of the NFTs held by such address are stored as an array.
This method MUST throw if assetCount(owner) >= index. This method MUST throw if index >= 2^128.
The DAR MAY change the order assigned to any NFT held by a particular address.
This method is expected to be used by other contracts to iterate through an owner's assets. Contracts implementing such iterations SHOULD be aware of race conditions that might occur, if this iteration happens over multiple transactions.
assetsOf(address owner):uint256[]
This method returns an array of IDs of the NFTs held by owner. This method SHALL NOT throw.
Operator getters
isAuthorizedBy(address operator, address owner):bool
This method returns true if owner has called the method authorize with parameters (operator, true) and has not called authorize(operator, false) afterwards. This method returns false otherwise.
This method MUST return true if operator == owner.
This method SHALL NOT throw.
isApprovedFor(address operator, uint256 assetId):bool
This method returns true if owner has called the method approve with parameters (operator, assetId) and has not called it with a different operator value afterwards. This method returns false otherwise.
This method MUST return true if operator == owner.
This method SHALL NOT throw.
Transfers
transfer(address to, uint256 assetId, bytes userData, bytes operatorData)
Transfers holding of the NFT referenced by assetId from ownerOf(assetId) to the address to.
to SHALL NOT be the zero address. If to is the zero address, the call MUST throw.
to SHALL NOT be ownerOf(assetId). If this condition is met, the call MUST throw.
isAuthorizedBy(msg.sender, ownerOf(assetId)) MUST return true as a precondition.
This means that the msg.sender MUST be ownerOf(assetId) or an authorized operator.
If the NFT referenced by assetId does not exist, then the call MUST throw.
If there was any single authorized operator (see the approve() method below), this authorization MUST be cleared.
If the call doesn't throw, it triggers the event Transfer with the following parameters:
- from: value of
ownerOf(assetId) before the call
- to: the
to argument
- assetId: the
assetId argument
- operator:
msg.sender
- userData: the
userData argument
- operatorData: the
operatorData argument
If to is a contract's address, this call MUST verify that the contract can receive the tokens. In order to do so, the method MUST do a #165 check for support to handle tokens to the target contract.
The implementation for the receiver is as follows:
interface IAssetHolder {
function onAssetReceived(
/* address _assetRegistry == msg.sender */
uint256 _assetId,
address _previousOwner,
address _currentOwner,
bytes _userData
) public;
}
If the supportsInterface method call fails, the transfer must be reversed and this call MUST throw. Otherwise, the method onAssetReceived MUST be invoked with the corresponding information, after the asset has been transferred.
transfer(address to, uint256 assetId, bytes userData)
Shorthand method that MUST be equivalent to calling transfer(to, assetId, userData, EMPTY_BYTES).
transfer(address to, uint256 assetId)
Shorthand method that MUST be equivalent to calling transfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES).
transferFrom(address from, address to, uint256 assetId, bytes userData, bytes operatorData)
Transfers holding of the NFT referenced by assetId from ownerOf(assetId) to the address to, iff from == ownerOf(assetId).
After checking that the asset is owned by the from address, this method MUST behave exactly as calling transfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES).
transferFrom(address from, address to, uint256 assetId, bytes userData)
Shorthand method that MUST be equivalent to calling transferFrom(to, assetId, userData, EMPTY_BYTES).
transferFrom(address from, address to, uint256 assetId)
Shorthand method that MUST be equivalent to calling transferFrom(to, assetId, EMPTY_BYTES, EMPTY_BYTES).
Authorization
authorize(address operator, bool authorized)
If authorized is true, allows operator to transfer any NFT held by msg.sender.
This method MUST throw if operator is the zero address. This method MUST throw if authorized is true and operator is already authorized by the sender. This method MUST throw if authorized is false and operator is unauthorized.
This method MUST throw if msg.sender == operator.
This method MUST trigger an AuthorizeOperator event if it doesn't throw.
approve(address operator, uint256 assetId)
Allow operator to transfer an asset without delegating full access to all assets.
Only the owner of an asset can call this method. Otherwise, the call MUST fail.
Only one address can receive approval at the same time. Clearing approval for a transfer can be done by setting the operator value to the zero address.
This method MUST trigger an Approve event if it doesn't throw.
Events
interface AssetRegistryEvents {
event Transfer(
address indexed from,
address indexed to,
uint256 indexed assetId,
address operator,
bytes userData,
bytes operatorData
);
event AuthorizeOperator(
address indexed operator,
address indexed holder,
bool authorized
);
event Approve(
address indexed owner,
address indexed operator,
uint256 indexed assetId
);
}
Interfaces
interface IAssetRegistry {
function totalSupply() public view returns (uint256);
function exists(uint256 assetId) public view returns (bool);
function ownerOf(uint256 assetId) public view returns (address);
function balanceOf(address holder) public view returns (uint256);
function assetByIndex(address holder, uint256 index) public view returns (uint256);
function assetsOf(address holder) external view returns (uint256[]);
function transfer(address to, uint256 assetId) public;
function transfer(address to, uint256 assetId, bytes userData) public;
function transfer(address to, uint256 assetId, bytes userData, bytes operatorData) public;
function transferFrom(address from, address to, uint256 assetId) public;
function transferFrom(address from, address to, uint256 assetId, bytes userData) public;
function transferFrom(address from, address to, uint256 assetId, bytes userData, bytes operatorData) public;
function approveAll(address operator, bool authorized) public;
function approve(address operator, uint256 assetId) public;
function isAuthorizedBy(address operator, address assetOwner) public view returns (bool);
function isApprovedFor(address operator, uint256 assetId) public view returns (bool);
function approvedFor(uint256 assetId) public view returns (address);
}
Implementation
https://github.com/decentraland/erc821
Revisions
- 2018/02/21: RC2, drop name, symbol, description
- 2018/02/21: RC1
- 2018/02/20: Drop metadata
- 2018/02/20: Add
transferFrom
- 2018/02/20: Try to remove
operator from public facing interfaces
- 2018/02/20: Simplify
holder -> owner to use the most common name
- 2018/02/20: Drop all ERC820 references
- 2018/02/03: Add
approve to approve individual assets to be transferred
- 2018/01/27: Add exception for
msg.sender being the manager
- 2018/01/27: Add table for
ERC820 and IAssetHolder interface check
- 2018/01/27: Change
IAssetOwner for IAssetHolder to reflect that another contract might hold tokens for one
- 2018/01/27: Errata on
transfer text
- 2018/01/26: Alias "balanceOf" to "assetCount" for ERC20 compatibility
- 2018/01/26: Add
decimals for more ERC20 compatibility
- 2018/01/26: Propose more metadata to be stored on-chain.
- 2018/01/26: Make EIP820 compatibility optional to receive tokens if
onAssetReceived(...) is implemented.
- 2018/01/26: Add
isERC821 flag to detect support.
- 2018/01/26: Revert
holder to owner.
- 2018/01/18:
transfer: to MUST NOT be holderOf(assetId)
- 2018/01/17: Added
safeAssetData method
- 2018/01/17: Clarification to
transfer: it MUST throw if the asset does not exist.
- 2018/01/17: Published first version of the specification
- 2018/01/16: Published implementation
- 2018/01/05: Initial draft
Copyright
Copyright and related rights waived via CC0.
Summary
A Distinguishable Assets Registry (DAR for short) is a contract that tracks ownership of, and information about a set of assets that are distinguishable from each other, and are commonly refferred to as "NFTs", for "Non Fungible Tokens".
See https://github.com/decentraland/erc821 for a reference implementation.
See the "Revisions" section for a history of this ERC.
Abstract
Tracking the ownership of physical or digital distinguishable items on a blockchain has a broad range of applications, from virtual collectibles to physical art pieces. This proposal aims at standardizing a way to reference distinguishable assets along with the foreseeable required operations and interfaces for effective management of those assets on a blockchain.
Introduction
The number of virtual collectibles tracked on the Ethereum blockchain is rapidly growing, creating a demand for a more robust standard for distinguishable digital assets. This proposal suggests improvements to the vocabulary used to refer to such assets, and attempts to provide a solid and future-proof reference implementation of the basic functionality needed. This EIP also proposes better naming conventions and vocabulary for the different components of the NFT economy: the assets, the NFTs (representations of those assets on the blockchain), the DARs (the contracts registering such assets), distinguishing ownership from holding addresses, and more.
See also: ERC #721, ERC #20, ERC #223, and ERC #777.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Specification
A non-fungible token (NFT) is a distinguishable asset that has a unique representation as a register in a smart contract. This proposal specifies such contracts, referenced as Distinguishable Asset Registries, or DARs. NFTs MAY also be reffered to as "deeds".
DARs can be identified by the blockchain in which they were deployed, and the 160-bit address of the contract instance. NFTs are identified by an ID, a 256 bit number, which MAY correspond to some cryptographic hash of the non-fungible's natural key.
NFTs SHOULD be referenced by a URI that follows this schema:
The NFT's ID SHOULD have a hint that helps to decode it. For instance, if the encoding of the number is in hexadecimal, the NFT’s ID SHOULD start with
0x. The DAR's address SHOULD follow the checksum by casing as proposed on #55.Some common names for Ethereum blockchains are:
ethereum,livenet, ormainnetropsten,testnetkovanrinkebySome examples of NFT URIs follow:
Every NFT MUST have a
owneraddress associated with it. NFTs associated with the null address are assumed non-existent or destroyed.An owner MAY assign one or multiple
operatoraddresses. These addresses will be able to transfer any asset of the owner.DARs MUST trigger
Transferevents every time a NFT'sownerchanges. This might happen under three circumstances:fromvalue of theTransferevent MUST be the zero address.tovalue of theTransferevent MUST be the zero address.Transferevents SHALL NOT simultaneously have a zero value in both thefromandtofields (this means, the sameTransferevent can't both create and destroy a token).Associated Metadata
Metadata associated with each asset is out of scope for this standard.
DAR global methods
totalSupply():uint256Return the total amount of assets under this DAR. This method SHALL NOT throw.
supportsInterface(bytes8 interfaceId):boolThis method returns
trueif theinterfaceIdis a supported interface (165, corresponding to 0x01ffc9a7, or 821, corresponding to 0x959d7abb). This method SHALL NOT throw.NFT getter methods
exists(uint256 assetId):boolThis method returns a boolean value,
trueif the asset identified with the givenassetIdexists under this DAR. This method SHALL NOT throw.ownerOf(uint256 assetId):addressThis method returns the
addressof the owner of the NFT. This method SHALL NOT throw. If theassetIddoes not exist, the return value MUST be the null address.Owner-centric getter methods
balanceOf(address owner):uint256This method returns the amount of NFTs held by the
owneraddress under this DAR. This method MUST not throw.assetByIndex(address owner, uint256 index):uint256This method returns the ID of the
indexth NFT held by theowneraddress under this DAR, when all the IDs of the NFTs held by such address are stored as an array.This method MUST throw if
assetCount(owner) >= index. This method MUST throw ifindex >= 2^128.The DAR MAY change the order assigned to any NFT held by a particular address.
This method is expected to be used by other contracts to iterate through an
owner's assets. Contracts implementing such iterations SHOULD be aware of race conditions that might occur, if this iteration happens over multiple transactions.assetsOf(address owner):uint256[]This method returns an array of IDs of the NFTs held by
owner. This method SHALL NOT throw.Operator getters
isAuthorizedBy(address operator, address owner):boolThis method returns
trueifownerhas called the methodauthorizewith parameters(operator, true)and has not calledauthorize(operator, false)afterwards. This method returnsfalseotherwise.This method MUST return
trueifoperator == owner.This method SHALL NOT throw.
isApprovedFor(address operator, uint256 assetId):boolThis method returns
trueifownerhas called the methodapprovewith parameters(operator, assetId)and has not called it with a different operator value afterwards. This method returnsfalseotherwise.This method MUST return
trueifoperator == owner.This method SHALL NOT throw.
Transfers
transfer(address to, uint256 assetId, bytes userData, bytes operatorData)Transfers holding of the NFT referenced by
assetIdfromownerOf(assetId)to the addressto.toSHALL NOT be the zero address. Iftois the zero address, the call MUST throw.toSHALL NOT beownerOf(assetId). If this condition is met, the call MUST throw.isAuthorizedBy(msg.sender, ownerOf(assetId))MUST return true as a precondition.This means that the
msg.senderMUST beownerOf(assetId)or an authorized operator.If the NFT referenced by
assetIddoes not exist, then the call MUST throw.If there was any single authorized operator (see the
approve()method below), this authorization MUST be cleared.If the call doesn't throw, it triggers the event
Transferwith the following parameters:ownerOf(assetId)before the calltoargumentassetIdargumentmsg.senderuserDataargumentoperatorDataargumentIf
tois a contract's address, this call MUST verify that the contract can receive the tokens. In order to do so, the method MUST do a #165 check for support to handle tokens to the target contract.The implementation for the receiver is as follows:
If the
supportsInterfacemethod call fails, the transfer must be reversed and this call MUST throw. Otherwise, the methodonAssetReceivedMUST be invoked with the corresponding information, after the asset has been transferred.transfer(address to, uint256 assetId, bytes userData)Shorthand method that MUST be equivalent to calling
transfer(to, assetId, userData, EMPTY_BYTES).transfer(address to, uint256 assetId)Shorthand method that MUST be equivalent to calling
transfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES).transferFrom(address from, address to, uint256 assetId, bytes userData, bytes operatorData)Transfers holding of the NFT referenced by
assetIdfromownerOf(assetId)to the addressto, ifffrom == ownerOf(assetId).After checking that the asset is owned by the
fromaddress, this method MUST behave exactly as callingtransfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES).transferFrom(address from, address to, uint256 assetId, bytes userData)Shorthand method that MUST be equivalent to calling
transferFrom(to, assetId, userData, EMPTY_BYTES).transferFrom(address from, address to, uint256 assetId)Shorthand method that MUST be equivalent to calling
transferFrom(to, assetId, EMPTY_BYTES, EMPTY_BYTES).Authorization
authorize(address operator, bool authorized)If
authorizedistrue, allowsoperatortotransferany NFT held bymsg.sender.This method MUST throw if
operatoris the zero address. This method MUST throw ifauthorizedis true andoperatoris already authorized by the sender. This method MUST throw ifauthorizedis false andoperatoris unauthorized.This method MUST throw if
msg.sender == operator.This method MUST trigger an
AuthorizeOperatorevent if it doesn't throw.approve(address operator, uint256 assetId)Allow
operatortotransferan asset without delegating full access to all assets.Only the
ownerof an asset can call this method. Otherwise, the call MUST fail.Only one address can receive
approvalat the same time. Clearing approval for a transfer can be done by setting theoperatorvalue to the zero address.This method MUST trigger an
Approveevent if it doesn't throw.Events
Interfaces
Implementation
https://github.com/decentraland/erc821
Revisions
transferFromoperatorfrom public facing interfacesholder->ownerto use the most common nameapproveto approve individual assets to be transferredmsg.senderbeing the managerERC820andIAssetHolderinterface checkIAssetOwnerforIAssetHolderto reflect that another contract might hold tokens for onetransfertextdecimalsfor more ERC20 compatibilityonAssetReceived(...)is implemented.isERC821flag to detect support.holdertoowner.transfer:toMUST NOT beholderOf(assetId)safeAssetDatamethodtransfer: it MUST throw if the asset does not exist.Copyright
Copyright and related rights waived via CC0.