EIPs/EIPS/eip-721.md

29 KiB

Preamble

EIP: <to be assigned>
Title: ERC-721 Deed Standard
Author: William Entriken <github.com@phor.net>, Dieter Shirley <dete@axiomzen.co>, Jacob Evans <jacob@dekz.net>, Nastassia Sachs <nastassia.sachs@protonmail.com>
Type: Standard
Category ERC
Status: Draft
Created: 2018-01-24
Requires: ERC-165

Simple Summary

A standard interface for deeds. Deeds express ownership of things.

Abstract

This is a standard interface for smart contracts to handle deeds. The scope of this interface includes interrogating the smart contract about deeds. It also allows transferring deeds. We consider use cases of deeds used by individuals as well as consignment to third party brokers/wallets/auctioneers.

The things to which deeds express ownership are an implementation detail. While writing this standard we considered a diverse univerise of things, and we know you will dream up many more:

  • Physical property — houses, unique artwork
  • Digital property — unique pictures of kittens, collectable cards
  • "Negative value" assets — loans, burdens and other responsibilities

In general, all houses are distinct and no two kittens are alike. Therefore you must track each deed separately; it is insufficient to simply count the deeds you own.

Motivation

A standard interface allows wallet/broker/auction applications to work with any deed on Ethereum. We provide for simple deed contracts as well as contracts that track an arbitrarily large number of deeds. Additional applications are discussed below.

This standard is inspired by the ERC-20 token standard and builds on two years of experience since EIP-20 was created. EIP-20 is insufficient for tracking deed ownership because each deed is distinct (non-fungible) whereas each of a quantity of tokens is identical (fungible).

Differences between this standard and EIP-20 are examined below.

Specification

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.

Every ERC-721 compliant contract must implement the ERC721 and ERC165 interfaces (subject to "caveats" below):

pragma solidity ^0.4.20;

/// @title Required part of ERC-721 Deed Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0xTODO_FILL_IN
interface ERC721 /* is ERC165 */ {
    /// @dev This emits when ownership of any deed changes by any mechanism.
    ///  This event emits when deeds are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of deeds
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that deed (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 _deedId);

    /// @dev This emits when the approved address for a single is changed or
    ///  reaffirmed. The zero address indicates there is no approved address.
    ///  When a Transfer event emits, this also indicates that the approved
    ///  address for that deed (if any) is reset to none.
    event Approval(address indexed _owner, address indexed _approved, uint256 _deedId);

    /// @dev This emits when an operator is enabled or disable for an owner.
    ///  The operator may manage all deeds of the owner.
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    /// @notice Count all deeds assigned to an owner
    /// @dev Deeds assigned to zero address are considered invalid, and this
    ///  function throws for queries about the zero address.
    /// @param _owner An address for whom to query the balance
    /// @return The number of deeds owned by `_owner`, possibly zero
    function balanceOf(address _owner) external view returns (uint256 _balance);

    /// @notice Find the owner of a deed
    /// @param _deedId The identifier for a deed we are inspecting
    /// @dev Deeds assigned to zero address are considered invalid, and queries
    ///  about them do throw.
    /// @return The address of the owner of the deed
    function ownerOf(uint256 _deedId) external view returns (address _owner);

    /// @notice Transfer ownership of a deed -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING DEEDS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current deed owner, an authorized
    ///  operator, or the approved address for this deed. Throws if `_from` is
    ///  not the current owner of the deed. Throws if `_to` is the zero address.
    ///  Throws if `_deedId` is not a valid deed.
    /// @param _from The new owner for the deed
    /// @param _to The new owner for the deed
    /// @param _deedId The deed to transfer
    function unsafeTransfer(address _from, address _to, uint256 _deedId) external payable;

    /// @notice Transfers the ownership of a given deed from one address to
    ///  another address
    /// @dev Throws unless `msg.sender` is the current deed owner, an authorized
    ///  operator, or the approved address for this deed. Throws if `_from` is
    ///  not the current owner of the deed. Throws if `_to` is the zero address.
    ///  Throws if `_deedId` is not a valid deed. When transfer is complete,
    ///  this function also calls `onNFTReceived` on `_to` and throws if the return
    ///  value is not `keccak256("ERC721_ONNFTRECEIVED")`.
    /// @param _from The current owner for the deed
    /// @param _to The new owner for the deed
    /// @param _deedId The deed to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
	function transferFrom(address _from, address _to, uint256 _deedId, bytes[] data) external payable;
	
	/// @notice Transfers the ownership of a given deed from one address to
    ///  another address
    /// @dev Throws unless `msg.sender` is the current deed owner, an authorized
    ///  operator, or the approved address for this deed. Throws if `_from` is
    ///  not the current owner of the deed. Throws if `_to` is the zero address.
    ///  Throws if `_deedId` is not a valid deed. When transfer is complete,
    ///  this function also calls `onNFTReceived` on `_to` and throws if the return
    ///  value is not `keccak256("ERC721_ONNFTRECEIVED")`.
    /// @param _from The current owner for the deed
    /// @param _to The new owner for the deed
    /// @param _deedId The deed to transfer
	function transferFrom(address _from, address _to, uint256 _deedId) external payable;

    /// @notice Set or reaffirm the approved address for a deed
    /// @dev The zero address indicates there is no approved address.
    /// @dev Throws unless `msg.sender` is the current deed owner, or an authorized
    ///  operator of the current deed owner.
    /// @param _approved The new approved deed controller
    /// @param _deedId The deed to approve
    function approve(address _approved, uint256 _deedId) external payable;

    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all your deeds.
    /// @dev Emits the ApprovalForAll event
    /// @param _operator Address to add to the set of authorized operators.
    /// @param _approved True if the operators is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external;

    /// @notice Get the approved address for a single deed
    /// @dev Throws if `_deedId` is not a valid deed
    /// @param _deedId The deed to find the approved address for
    /// @return The approved address for this deed, or the zero address if there is none
    function getApproved(uint256 _deedId) returns (address);

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the deeds
    /// @param _operator The address that acts on behalf of the owner
    /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
	function isApprovedForAll(address _owner, address _operator) returns (bool);
}

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.

interface ERC721TokenReceiver {
    /// @notice Handle the receipt of a token
    /// @dev The ERC721 smart contract calls this function on the recipient
    ///  after a `transfer`. This function MAY throw to revert and reject the
    ///  transfer. This function MUST use 50,000 gas or less. Return of other
    ///  than the magic value MUST result in the transaction being reverted.
    ///  Note: the contract address is always the message sender.
    /// @param _from The sending address 
    /// @param _tokenId The deed identifier which was sent
    /// @param data Additional data with no specified format
    /// @return Always returns `keccak256("ERC721_ONNFTRECEIVED")`, unless throwing
	function onNFTReceived(address _from, uint256 _tokenId, bytes data) external returns(bytes4);
}

The metadata extension is OPTIONAL for ERC-721 smart contracts (see "caveats", below). This allows your contract to be interrogated for its name and for details about the things.

/// @title Optional metadata extension to ERC-721 Deed Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x2a786f11
interface ERC721Metadata /* is ERC721 */ {
    /// @notice A descriptive name for a collection of deeds in this contract
    function name() external pure returns (string _name);

    /// @notice An abbreviated name for deeds in this contract
    function symbol() external pure returns (string _symbol);

    /// @notice A distinct Uniform Resource Identifier (URI) for a given deed.
    /// @dev Throws if `_deedId` is not a valid deed. URIs are defined in RFC
    ///  3986. The URI may point to a JSON file that conforms to the "ERC721
    ///  Metadata JSON Schema".
    function deedUri(uint256 _deedId) external view returns (string _deedUri);
}

This is the "ERC721 Metadata JSON Schema" referenced above. Learn more about JSON schemas.

{
    "title": "Deed Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the thing to which the deed grants ownership",
        },
        "description": {
            "type": "string",
            "description": "Describes the thing to which the deed grants ownership",
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the thing to which the deeds grants ownership. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.",
        }
    }
}

The enumeration extension is OPTIONAL for ERC-721 smart contracts (see "caveats", below). This allows your contract to publish the the full list of deeds and make them discoverable.

/// @title Optional enumeration extension to ERC-721 Deed Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x5576ab6a
interface ERC721Enumerable /* is ERC721 */ {
    /// @notice Count deeds tracked by this contract
    /// @return A count of valid deeds tracked by this contract, where each one of
    ///  them has an assigned and queryable owner not equal to the zero address
    function totalSupply() external view returns (uint256 _count);

    /// @notice Enumerate active deeds
    /// @dev Throws if `_index` >= `countOfDeeds()`.
    ///  Otherwise must not throw.
    /// @param _index A counter less than `countOfDeeds()`
    /// @return The identifier for the `_index`th deed, (sort order not
    ///  specified)
    function deedByIndex(uint256 _index) external view returns (uint256 _deedId);

    /// @notice Count of owners which own at least one deed
    ///  Must not throw.
    /// @return A count of the number of owners which own deeds
    function countOfOwners() external view returns (uint256 _count);

    /// @notice Enumerate owners
    /// @dev Throws if `_index` >= `countOfOwners()`
    ///  Otherwise must not throw.
    /// @param _index A counter less than `countOfOwners()`
    /// @return The address of the `_index`th owner (sort order not specified)
    function ownerByIndex(uint256 _index) external view returns (address _owner);

    /// @notice Enumerate deeds assigned to an owner
    /// @dev Throws if `_index` >= `countOfDeedsByOwner(_owner)` or if
    ///  `_owner` is the zero address, representing invalid deeds.
    ///  Otherwise this must not throw.
    /// @param _owner An address where we are interested in deeds owned by them
    /// @param _index A counter less than `countOfDeedsByOwner(_owner)`
    /// @return The identifier for the `_index`th deed assigned to `_owner`,
    ///   (sort order not specified)
    function deedOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 _deedId);
}

Caveats

The 0.4.20 Solidity interface grammar is not expressive enough to document the ERC-721 specification. A contract which complies with ERC-721 MUST also abide by the following:

  • Solidity issue #3412: The above interfaces include explicit mutability guarantees for each function. Mutability guarantees are, in order weak to strong: payable, implicit nonpayable, view, and pure. Your implementation must meet the mutability guarantee in this interface or you may meet a stronger guarantee. For example, a payable function in this interface may be implemented as nonpayble (no state mutability specified) in your contract. We expect a later Solidity release will allow your stricter contract to inherit from this interface, but a workaround for version 0.4.20 is that you can edit this interface to add stricter mutability before inheriting from your contract.
  • Solidity issue #3419: A contract that implements ERC721Metadata or ERC721Enumerable SHALL also implement ERC721. ERC-721 implements the requirements of interface ERC-165.
  • Solidity issue #2330: If a function is shown in this specification as external then a contract will be compliant if it uses public visibility.
  • Solidity issues #3494, #3544: Use of this.*.selector is marked as a warning by Solidity, a future version of Solidity will not mark this as an error.

If a newer version of Solidity allows the caveats to be expressed in code, then this EIP MAY be updated and the caveats removed, such will be equivalent to the original specification.

Rationale

There are many proposed uses of Ethereum smart contracts that depend on tracking individual deeds (non-fungible tokens). Examples of existing or planned NFTs are LAND in Decentraland, the eponymous punks in CryptoPunks, and in-game items using systems like Dmarket or EnjinCoin. Future uses include tracking real-world non-fungible assets, like real-estate (as envisioned by companies like Ubitquity or Propy). It is critical in each of these cases that these items are not "lumped together" as numbers in a ledger, but instead, each thing must have its ownership individually and atomically tracked. Regardless of the nature of these things, the ecosystem will be stronger if we have a standardized interface that allows for cross-functional deed management and sales platforms.

"Deed" word choice

The noun deed is defined in the Oxford Dictionary as:

A legal document that is signed and delivered, especially one regarding the ownership of property or legal rights.

This definition is consistent with the fact that ERC-721 contracts track ownership of other things, such as houses, pictures of kittens or collectable cards. If you gain ownership of a house via ERC-721 then the ERC-721 contract manages the deed and the place where you live is the thing.

We chose specifically to avoid the word "token" because it has a well known, and different, meaning in the Ethereum ecosystem, specifically ERC-20 tokens.

Alternatives considered: non-fungible token, title, token, asset, equity, ticket

Deed identifiers

The basis of this standard is that every deed is identified by a unique 256-bit unsigned integer within its tracking contract. This ID number SHALL NOT change for the life of the contract. The pair (contract address, deed ID) will then be a globally unique and fully-qualified identifier for a specific deed within the Ethereum ecosystem. While some contracts may find it convenient to start with ID 0 and simply increment by one for each new deed, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a "black box". Also note that a deeds MAY become invalid (be destroyed). Please see the enumerations functions for a supported enumeration interface.

The choice of uint256 allows a wide variety of applications because UUIDs and sha3 hashes are directly convertible to uint256.

Transfer mechanism

ERC-721 standardizes a safe transfer function transferFrom (overloaded with and without a bytes parameter) and an unsafe function unsafeTransfer. Transfers may be initiated by:

  • The owner of a deed
  • The approved address of a deed
  • An authorized operator of the current owner of a deed

Additionally, an authorized operator may set the approved address for a deed. This provides a powerful set of tools for wallet, broker or auction applications to quickly use a large number of deeds.

The transfer and accept functions documentation only specify conditions when the transaction MUST throw. Your implementation MAY also throw in other situations. This allows implementations to achieve interesting results:

  • Disallow transfers if the contract is paused — prior art, Crypto Kitties
  • Blacklist certain address from receiving deeds — prior art, Crypto Kitties, (lines 565, 566).
  • Disallow unsafe transfersunsafeTransfer throws unless _to equals msg.sender or countOf(_to) is non-zero (because such cases are safe)
  • Charge a fee to both parties of a transaction — require payment when calling approve with a non-zero _approved if it was previously the zero address, refund payment if calling approve with the zero address if it was previously a non-zero address, require payment when calling transfer, require transfer parameter _to to equal msg.sender, require transfer parameter _to to be the approved address for the deed
  • Read only deed registry — always throw from unsafeTransfer, transferFrom, approve and setApprovalForAll

Failed transactions will throw, a best practice identified in ERC-233 , ERC-677, ERC-827 and OpenZeppelin. ERC-20 defined an allowance feature, this caused a problem when called and then later modified to a different amount, as disucssed on OpenZeppelin. In ERC-721, there is no allowance because every deed is unique, the quantity is none or one. Therefore we receive the benefits of ERC-20's original design without problems that have been later discovered.

Creating of new deeds ("minting") and destruction of deeds ("burning") is not included in the specification. Your contract may implement these by other means. Please see the event documentation for your responsibilities when creating or destroying deeds.

Alternatives considered: only allow two-step ERC-20 style transaction, require that transfer never throw, require all functions to return a boolean indicating the success of the operation.

ERC-165 interface

We chose Standard Interface Detection ERC-165 to expose the interfaces that a ERC-721 smart contract supports.

A future EIP may create a global registry of interfaces for contracts. We strongly support such an EIP and it would allow your contract to to implement ERC721Enumerable, ERC721Metadata, or other interfaces by delegating to a separate contract.

Gas and complexity (regarding the enumeration extension)

This specification contemplates implementations that manage a few and arbitrarily large numbers of deeds. If your application is able to grow then avoid using for/while loops in your code. These indicate your contract may be unable to scale and gas costs will rise over time without bound.

We have deployed a contract to test net which instantiates and tracks 340282366920938463463374607431768211456 different deeds (2^128). That's enough to assign every IPV6 address to an Ethereum account owner, or to track ownership of nanobots a few micron in size and in aggregate totalling half the size of Earth. You can query it from the blockchain. And every function takes less gas than querying the ENS.

This illustration makes clear: the Deed Standard scales.

Alternatives considered: remove the deed enumeration function if it requries a for-loop, return a Soldity array type from enumeration functions.

Privacy

Wallets/brokers/auctioneers identified in the motivation section have a strong need to identify which deeds an owner owns.

It may be interesting to consider a use case where deeds are not enumerable or deeds are not enumerable by owner, such as a private registry of property ownership, or a partially-private registry. However, privacy cannot be attained because an attacker can simply (!) call ownerOf for every possible deedId.

Metadata choices (metadata extension)

We have required name and symbol functions in the metadata extension. Every token EIP and draft we reviewed (ERC-20, ERC-233 , ERC-677, ERC-777, ERC-827) included these functions.

We remind implementation authors that the empty string is a valid response to name and symbol if you protest to the usage of this mechanism. We also remind everyone that the official contract for tracking 0xProject tokens (ZRX) is 0xe41d2489571d322189246dafa5ebde1f4699f498. Another contract that advertises a name of 0xProject and symbol ZRX is not the well-known (canonical) contract.

How a client may determine which token and deed contracts are well-known is outside the scope of this standard.

A mechanism is provided to associate deeds with URIs. We expect that many implementations will take advantage of this to provide metadata for each deed. The image size recommendation is taken from Instagram, they probably know much about image usability. The URI MAY be mutable (i.e. it changes from time to time). We considered a deed representing ownership of a house, in this case metadata about the house (image, occupants, etc.) may naturally change.

Metadata is returned as a string value. Currently this is only usable as calling from web3, not from other contracts. This is acceptable because we have not considered a use case where an on-blockchain application would query such information.

Alternatives considered: put all metadata for each deed on the blockchain (too expensive), use URL templates to query metadata parts (URL templates do not work with all URL schemes, especially P2P URLs), multiaddr network address (not mature enough)

Community consensus

A significant amount of discussion occurred on [the original ERC-721](ERC-721 issue), additionally we held a live meeting on Gitter that had good representation and was well advertised in relevant communities. Thank you to the participants:

  • @ImAllInNow Rob from DEC Gaming / Presenting Michigan Ethereum Meetup Feb 7
  • @Arachnid Nick Johnson
  • @jadhavajay Ajay Jadhav from AyanWorks
  • @superphly Cody Marx Bailey - XRAM Capital / Sharing at hackathon Jan 20 / UN Future of Finance Hackathon.

A second event was held at ETHDenver 2018 to discuss deed standards (notes to be published).

We have been very inclusive in this process and invite anyone with questions or contributions into our discussion. However, this standard is written only to support the identified use cases which are listed herein.

Backwards Compatibility

We have adopted balanceOf, totalSupply, name and symbol semantics from the ERC-20 specification. An implementation may also include a function decimals that returns uint8(0) if its goal is to be more compatible with ERC-20 while supporting this standard. However, we find it contrived to require all ERC-721 implementations to support the decimals function.

Example deeds implementations as of February 2018:

  • CryptoKitties — Compatible with an earlier version of this standard.
  • CryptoPunks — Partially ERC-20 compatible, but not easily generalizable because it includes auction functionality directly in the contract and uses function names that explicitly refer to the deeds as "punks".
  • Auctionhouse Asset Interface@dob needed a generic interface for his Auctionhouse dapp (currently ice-boxed). His "Asset" contract is very simple, but is missing ERC-20 compatibility, approve() functionality, and metadata. This effort is referenced in the discussion for EIP-173.

Note: "Limited edition, collectible tokens" like Curio Cards and Rare Pepe are not deeds. They're actually a collection of individual fungible tokens, each of which is tracked by its own smart contract with its own total supply (which may be 1 in extreme cases).

The onNFTReceived function specifically works around old deployed contracts which may inadvertently return 1 (true) in certain circumstances even if they don't implement a function. By returning, and checking for, a magic value we are able to distinguish actual affirmative responses versus these trues.

Test Cases

TO DO

Test cases for an implementation are mandatory for EIPs that are affecting consensus changes. Other EIPs can choose to include links to test cases if applicable.

Implementations

ERC721ExampleDeed, by Nastassia Sachs: https://github.com/nastassiasachs/ERC721ExampleDeed

  • Implements using the Open Zeppelin project format

XXXXERC721, by William Entriken: https://github.com/fulldecent/erc721-example

  • Deployed on testnet with 1 billion deeds and supporting all lookups with the metadata extension. This demonstrates that scaling is NOT a problem.

Copyright and related rights waived via CC0.