Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-721 Non-Fungible Token Standard #841

Merged
merged 54 commits into from
Mar 9, 2018
Merged

Conversation

fulldecent
Copy link
Contributor

@fulldecent fulldecent commented Jan 24, 2018

STATUS: DRAFT STANDARD PUBLISHED, SEE https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md

Thanks for all your help and hard work!


How to discuss

If you... Then please discuss at...
Want to discuss YOUR application https://gitter.im/ETH-NFT/Lobby
Have general thoughts/questions on NFT/deed standardization #721
Reviewed this pull request and have implementation feedback or can help improve the wording in the standard Comment here, scroll down ⬇

And you can always reach out to me directly.

  • Please introduce yourself 😃
  • If you express an opinion, are you speaking individually or do you represent an organization?
  • If you provide feedback, what level of review you have performed (creative thinking/test code/deployed contract)?

TO DO:

  • Write rationale section
  • Finalize ERC721Metadata interface
  • Finalize ERC165 compliance
  • Reconsider the "deed" word choice -- Reconsider the word "deed" fulldecent/EIPs#2
  • Demonstrate sufficient consensus for standardization
  • Launch bug bounty program (bounty ended 2018-03-04)
  • Review which event parameters are indexed
  • **Submit to EIP reviewer for DRAFT ACCEPTED
  • OPEN ITEM -- should setApprovedForAll allow the zero address?
  • Get implementation into OpenZeppelin
  • Write test cases / Truffle
  • Post on Reddit, wider forum promote adoption
  • Requires ERC-165 (currently in DRAFT ACCEPTED status)
  • Get to DRAFT APPROVED status

What is this?

This pull request is the ERC-721 standardization document.

This PR is represents the contributions of dozens of people imagining a future where smart contracts will manage ownership of important things in the real world and online. Thanks for your help and support!

Please react with party emoji 🎉 if you believe this EIP should be accepted. Acceptance is NOT final. The significance of acceptance is detailed in EIP-1:

If the EIP collaborators approve, the EIP editor will assign the EIP a number (generally the issue or PR number related to the EIP), label it as Standards Track, Informational, or Meta, give it status “Draft”, and add it to the git repository.

When this is accepted, then we can begin the work of writing test cases and implementations. We can still discuss and change things. Another benefit is that it gets listed on the EIP homepage, this increases the audience of this EIP and will get more people involved and scrutinizing this standard.

You are the collaborators.

# TO DO:

- [ ] Get to status accepted
- [ ] Write test cases
- [ ] Write implementation (on OpenZeppelin?)
- [ ] Get to ​status final
@fulldecent
Copy link
Contributor Author

fulldecent commented Jan 24, 2018

For history.

Version 39067a2 / 2018-03-08 / just before draft accepted ---

Preamble

EIP: <to be assigned>
Title: ERC-721 Non-Fungible Token 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 non-fungible tokens, also known as deeds.

Abstract

The following standard allows for the implementation of a standard API for NFTs within smart contracts. This standard provides basic functionality to track and transfer NFTs.

We considered use cases of NFTs being owned and transacted by individuals as well as consignment to third party brokers/wallets/auctioneers ("operators"). NFTs can represent ownership over digital or physical assets. We considered a diverse universe of assets, and we know you will dream up many more:

  • Physical property — houses, unique artwork
  • Virtual collectables — 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. NFTs are distinguishable and you must track the ownership of each one separately.

Motivation

A standard interface allows wallet/broker/auction applications to work with any NFT on Ethereum. We provide for simple ERC-721 smart contracts as well as contracts that track an arbitrarily large number of NFTs. 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 NFTs because each asset 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 ERC-721 Non-Fungible Token Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x6466353c
interface ERC721 /* is ERC165 */ {
    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

    /// @dev This emits when the approved address for an NFT 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 NFT (if any) is reset to none.
    event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

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

    /// @notice Count all NFTs assigned to an owner
    /// @dev NFTs assigned to the 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 NFTs owned by `_owner`, possibly zero
    function balanceOf(address _owner) external view returns (uint256);

    /// @notice Find the owner of an NFT
    /// @param _tokenId The identifier for an NFT
    /// @dev NFTs assigned to zero address are considered invalid, and queries
    ///  about them do throw.
    /// @return The address of the owner of the NFT
    function ownerOf(uint256 _tokenId) external view returns (address);

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
    ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
    ///  `onERC721Received` on `_to` and throws if the return value is not
    ///  `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
	
    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to ""
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

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

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

    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all your asset.
    /// @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 NFT
    /// @dev Throws if `_tokenId` is not a valid NFT
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if there is none
    function getApproved(uint256 _tokenId) external view returns (address);

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the NFTs
    /// @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) external view 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.

/// @dev Note: the ERC-165 identifier for this interface is 0xf0b9e5ba
interface ERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @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 NFT identifier which is being transfered
    /// @param data Additional data with no specified format
    /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
    ///  unless throwing
	function onERC721Received(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 smart contract to be interrogated for its name and for details about the assets which your NFTs represent.

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

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

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

This is the "ERC721 Metadata JSON Schema" referenced above.

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents",
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents",
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. 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 its full list of NFTs and make them discoverable.

/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x780e9d63
interface ERC721Enumerable /* is ERC721 */ {
    /// @notice Count NFTs tracked by this contract
    /// @return A count of valid NFTs 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);

    /// @notice Enumerate valid NFTs
    /// @dev Throws if `_index` >= `totalSupply()`.
    /// @param _index A counter less than `totalSupply()`
    /// @return The token identifier for the `_index`th NFT,
    ///  (sort order not specified)
    function tokenByIndex(uint256 _index) external view returns (uint256);

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

Caveats

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

  • Solidity issue 3368: Fixes typo of = instead of == #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 and 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 Micah zoltu patch  #3419: A contract that implements ERC721Metadata or ERC721Enumerable SHALL also implement ERC721. ERC-721 implements the requirements of interface ERC-165.
  • Solidity issue Added EIP-2330: EXTSLOAD #2330: If a function is shown in this specification as external then a contract will be compliant if it uses public visibility. As a workaround for version 0.4.20, you can edit this interface to switch to public before inheriting from your contract.
  • Solidity issues Require at least one Github handle in author field #3494, Allowing multiple addresses to own one address. #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 distinguishable assets. 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 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 asset must have its ownership individually and atomically tracked. Regardless of the nature of these assets, the ecosystem will be stronger if we have a standardized interface that allows for cross-functional asset management and sales platforms.

"NFT" Word Choice

"NFT" was satisfactory to nearly everyone surveyed and is widely applicable to a broad universe of distinguishable digital assets. We recongize that "deed" is very descriptive for certain applications of this standard (notably, physical property).

Alternatives considered: distinguishable asset, title, token, asset, equity, ticket

NFT Identifiers

Every NFT is identified by a unique uint265 ID inside the ERC-721 smart contract. This identifing number SHALL NOT change for the life of the contract. The pair (contract address, uint265 tokenId) will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, 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 NFTs 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 safeTransferFrom (overloaded with and without a bytes parameter) and an unsafe function transferFrom. Transfers may be initiated by:

  • The owner of an NFT
  • The approved address of an NFT
  • An authorized operator of the current owner of an NFT

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

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, CryptoKitties deployed contract, line 611
  • Blacklist certain address from receiving NFTs — prior art, CryptoKitties deployed contract, lines 565, 566
  • Disallow unsafe transferstransferFrom throws unless _to equals msg.sender or countOf(_to) is non-zero or was non-zero previously (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 any transfer function, require transfer parameter _to to equal msg.sender, require transfer parameter _to to be the approved address for the NFT
  • Read only NFT registry — always throw from unsafeTransfer, transferFrom, approve and setApprovalForAll

Failed transactions will throw, a best practice identified in ERC-223, ERC-677, ERC-827 and OpenZeppelin's implementation of SafeERC20.sol. ERC-20 defined an allowance feature, this caused a problem when called and then later modified to a different amount, as on OpenZeppelin issue #438. In ERC-721, there is no allowance because every NFT 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 NFTs ("minting") and destruction NFTs ("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 NFTs.

Alternatives considered: only allow two-step ERC-20 style transaction, require that transfer functions 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 ERC-721 implementation 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 NFTs. If your application is able to grow then avoid using for/while loops in your code (see CryptoKitties bounty issue #4). These indicate your contract may be unable to scale and gas costs will rise over time without bound.

We have deployed a contract, XXXXERC721, to Testnet 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 ERC-721 standard scales.

Alternatives considered: remove the asset 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 NFTs an owner owns.

It may be interesting to consider a use case where NFTs are not enumerable, 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 tokenId.

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-223, 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 any smart contract can use the same name and symbol as your contract. How a client may determine which ERC-721 smart contracts are well-known (canonical) is outside the scope of this standard.

A mechanism is provided to associate NFTs with URIs. We expect that many implementations will take advantage of this to provide metadata for each NFT. 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 an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can 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 asset 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 issue, additionally we held a first live meeting on Gitter that had good representation and well advertised (on Reddit, in the Gitter #ERC channel, and the original ERC-721 issue). 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.
  • @fulldecent William Entriken

A second event was held at ETHDenver 2018 to discuss distinguishable asset 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 NFT 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 assets as "punks".
  • Auctionhouse Asset Interface -- The author needed a generic interface for the Auctionhouse ÐApp (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 distinguishable assets. 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 onERC721Received function specifically works around old deployed contracts which may inadvertently return 1 (true) in certain circumstances even if they don't implement a function (see Solidity DelegateCallReturnValue bug). By returning and checking for a magic value, we are able to distinguish actual affirmative responses versus these vacuous 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

Su Squares -- an advertising platform where you can rent space and place images

  • Complete the Su Squares Bug Bounty Program to seek problems with this standard or its implementation
  • Implements the complete standard and all optional interfaces

ERC721ExampleDeed -- an example implementation

  • Implements using the OpenZeppelin project format

XXXXERC721, by William Entriken -- a scalable example implementation

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

References

Standards

  1. ERC-20 Token Standard. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
  2. ERC-165 Standard Interface Detection. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md
  3. ERC-173 Owned Standard. ERC: Owned Standard #173
  4. ERC-223 Token Standard. ERC223 token standard #223
  5. ERC-677 transferAndCall Token Standard. ERC: transferAndCall Token Standard #677
  6. ERC-827 Token Standard. ERC827 Token Standard (ERC20 Extension) #827
  7. Ethereum Name Service (ENS). https://ens.domains
  8. Instagram -- What's the Image Resolution? https://help.instagram.com/1631821640426723
  9. JSON Schema. http://json-schema.org/
  10. Multiaddr. https://github.com/multiformats/multiaddr
  11. RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt

Issues

  1. The Original ERC-721 Issue. ERC: Non-fungible Token Standard #721
  2. Solidity Issue Added EIP-2330: EXTSLOAD #2330 -- Interface Functions are Axternal. Interface functions are external, or no interfaces solidity#2330
  3. Solidity Issue 3368: Fixes typo of = instead of == #3412 -- Implement Interface: Allow Stricter Mutability. Inheritance: allow stricter mutability solidity#3412
  4. Solidity Issue Micah zoltu patch  #3419 -- Interfaces Can't Inherit. Interfaces can't inherit solidity#3419
  5. Solidity Issue Require at least one Github handle in author field #3494 -- Compiler Incorrectly Reasons About the selector Function. Compiler incorrectly reasons about the selector function solidity#3494
  6. Solidity Issue Allowing multiple addresses to own one address. #3544 -- Cannot Calculate Selector of Function Named transfer. Cannot calculate selector of function named transfer solidity#3544
  7. CryptoKitties Bounty Issue Add a Gitter chat badge to README.md #4 -- Listing all Kitties Owned by a User is O(n^2). Listing all kitties owned by a user is O(n^2) dapperlabs/cryptokitties-bounty#4
  8. OpenZeppelin Issue avsa aided the dao attacker <3 #438 -- Implementation of approve method violates ERC20 standard. Implementation of approve method violates ERC20 standard OpenZeppelin/openzeppelin-contracts#438
  9. Solidity DelegateCallReturnValue Bug. http://solidity.readthedocs.io/en/develop/bugs.html#DelegateCallReturnValue

Discussions

  1. Reddit (announcement of first live discussion). https://www.reddit.com/r/ethereum/comments/7r2ena/friday_119_live_discussion_on_erc_nonfungible/
  2. Gitter #EIPs (announcement of first live discussion). https://gitter.im/ethereum/EIPs?at=5a5f823fb48e8c3566f0a5e7
  3. ERC-721 (announcement of first live discussion). ERC: Non-fungible Token Standard #721 (comment)
  4. ETHDenver 2018. https://ethdenver.com

NFT Implementations and Other Projects

  1. CryptoKitties. https://www.cryptokitties.co
  2. Su Squares. https://tenthousandsu.com
  3. Decentraland. https://decentraland.org
  4. CryptoPunks. https://www.larvalabs.com/cryptopunks
  5. DMarket. https://www.dmarket.io
  6. Enjin Coin. https://enjincoin.io
  7. Ubitquity. https://www.ubitquity.io
  8. Propy. https://tokensale.propy.com
  9. CryptoKitties Deployed Contract. https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code
  10. Su Squares Bug Bounty Program. https://github.com/fulldecent/su-squares-bounty
  11. XXXXERC721. https://github.com/fulldecent/erc721-example
  12. ERC721ExampleDeed. https://github.com/nastassiasachs/ERC721ExampleDeed
  13. Curio Cards. https://mycuriocards.com
  14. Rare Pepe. https://rarepepewallet.com
  15. Auctionhouse Asset Interface. https://github.com/dob/auctionhouse/blob/master/contracts/Asset.sol
  16. OpenZeppelin SafeERC20.sol Implementation. https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/SafeERC20.sol

Copyright

Copyright and related rights waived via CC0.

@fulldecent
Copy link
Contributor Author

@Nanolucas
Copy link

I assume this is meant to supersede/continue the original #721 but I don't understand why everything is now called a deed instead of an NFT. That was never part of the discussion in 721 and I don't see why that needed to change.

Also is it not inherently a risk to include an implementation of a another draft standard (#165) in what we hope will become the standard for NFTs? What will happen if/when #165 changes?

I fully support a push to finalise the standard since it seems dete has other priorities at the moment, but I don't agree with changing things just for the sake of it. Referring to NFTs and "tokenIds" instead of 'deeds' and "deedIds" seems more inclusive to me.

@tomcur
Copy link

tomcur commented Jan 24, 2018

I like the standard in general. Thank you for pushing for finalization.

I agree with @Nanolucas the change to "deed" seems odd. This then extends to function deedUri, which used to be tokenMetadata, thereby somewhat breaking backwards compatibility with existing implementations.

There is a typo in the interface code given. For ERC721Enumerable, INTERFACE_SIGNATURE_ERC721Metadata is specified instead of INTERFACE_SIGNATURE_ERC721Enumerable.


Text typos:

  • the one-step transfer(address _to, uint256 _value) is underirible.
    -> the one-step transfer(address _to, uint256 _value) is undesirible.

  • A careful reading of this standars approve and takeOwnership functions also shows
    -> A careful reading of this standard's approve and takeOwnership functions also shows

  • Gas and compleixty
    -> Gas and complexity

  • All fuctnios in the baseline
    -> All functions in the baseline

  • just the most usefull
    -> just the most useful

@ajile-in
Copy link

Nice one to see things are summing up clearly, and of course the push for finalisation.

I too agree with @Nanolucas and @Beskhue about the use of "deed" word instead "NFTs" or "Asset" that was discussed during the gitter chat.

@TCOA
Copy link

TCOA commented Jan 24, 2018

As a person more focused on real estate (and very new to blockchain), I like "deed" (and the consistency of "deedId") or "title" (though it is horribly generic) as this relates to terms used in the real estate world - however, as a developer/programmer planning to work with this, I think care (and respect of prior discussion) should be used to not confuse. I'm not a fan of "NFT" (means nothing to someone not familiar with the history of how it came about - how easy will that be to recognize for future users?). Other terms, such as Certificate of Ownership and Certificate of Title don't lend themselves to short wording - so, "deed" (and a consistent use of that throughout function naming, etc.), to me, is the best choice - easy to remember and find on searches years from now, etc...

============

In the main doc, this should not be in there.....
"(first, that would be awesome)" and "(and invite Will for dinner)"

=============

Change (for clarity)
While I am very happy to see Function mutability in the standard, it is not clear to me (yet) that this means "any/all fees including miner's fees, pre-existing fees and/or taxes" where 'fees' are various things that may be applied in the original contract (in the real estate world there are such things called "transfer fees" which, once applied, stay with the property forever {rarely are there time limitations, though that should also be considered} and require a fee be paid to the original owner/family and/or others every time the property is transferred) as well as 'tax' (which is a governmental assessment). Perhaps this is all done in the contract details (forgive my lack of full understanding - I'm quickly learning!)

It is also not clear to me on how to split the fees, which is another common thing in real estate - it can be any amount as agreed by the two parties - usually done in % of the total or a set amount as in "I will pay up to $X of the fees - anything over that, you pay". Is it envisioned that each individual transfer of ownership will have the option to set this for each party (and perhaps a set 'standard' amount in the contact)?

Therefore, I think
"where transfer of property will require paying tax from the old and/or new owner"
should be
"where transfer of property will require paying tax and/or various fees (including, but not limited to mining fees and pre-set transference fees), which may be paid by the old and/or new owner in any pre-arranged amount"

I'm happy to see any wording that helps clarify the above - I think it applies to any real-world transaction, I've included only details from my experience in real estate.

=======

In addition to the typos shown, I question " or they may return an empty string / unable value." - should that be "unusable"?

@TCOA
Copy link

TCOA commented Jan 24, 2018

One other thing that I realized - in real-world application of Deeds - and certainly for utilizing this technology as a replacement for a Public Records system - there is a requirement for retaining not only an owner by address, but by their legal name as well as a mechanism to search by such name (or portion thereof, for most Public Records searches). I understand this is 0% privacy, though that is what make it 'Public Records'....

I think this would fit fine in the ERC-721 Accountability Extension, simply adding on an ownerByName (or ownerByLegalName ?) function.

EIPS/eip-721.md Outdated
Type: Standard
Category ERC
Status: Draft
Created: 2018-01-31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a future date.

EIPS/eip-721.md Outdated

## Abstract

**This a standard interface for smart contracts to handle deed ownership.** Deeds can represent ownership of physical property, like houses, or digital property, like unique pictures of kittens. 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This a standard interface

"is" is missing.

@fulldecent
Copy link
Contributor Author

fulldecent commented Jan 24, 2018

Regarding "deed" name

@Beskhue, backwards compatibility with existing contracts is not a design goal of this specification. Such is the risk of implementing standards that are not finalized. Please note that ERC-20 was designed as a consensus standard to meet existing implementations, and it suffered accordingly.


I argue that deeds are more general than tokens (and, by extension, NFTs). The word title is probably more correct, but title has other unrelated meanings that are much better known.

CASE STUDY, CRYPTO KITTIES: See the contract KittyOwnership code. In this case, the ownership contract concerns itself with who owns each kitten. This is distinct from the kitten itself. The kitten is defined in struct Kitty. (Warning: the links in this case study are an older version of CK, but they are representative of the actual deployed code.)

CASE STUDY, HOUSES: In most (all?) counties in the United Status, house ownership is recorded by a county deed recorder, the instrument of house ownership is a deed. The deed is distinct from the house. In fact, the house may change, the taxes on the house might change. However the deed is permanent, until it is transferred.

These two case studies illustrate how a deed is separate from the thing which it declares ownership over. In token standards, the token itself is what you have ownership over. In deed standards, you always want ownership of a separate thing. Such is the motivation for the distinct name.

^^^ If there is a more elegant way to explain this, I'd like to include it in the EIP.

@fulldecent
Copy link
Contributor Author

fulldecent commented Jan 24, 2018

@TCOA, thank you, I have incorporated these updates. As for transfer mechanisms that include transference fees, I believe this standard does sufficiently allow all such implementations.

As for searching by owner. One implementation may implement this by only allowing smart contracts to have ownership of the deeds. Those smart contracts may have additional requirements, such as having a name. Additionally, an implementation may require additional metadata be recorded with each deed. These types of applications are considered and are applicable with this standard. However, I do not see that they should be required for the standard.

@fulldecent
Copy link
Contributor Author

We are moving along, and thanks for all your help, seriously.

Please scroll up and vote to accept this EIP --> 🎉

Accepting does not mean you agree with everything. It means we should list this as an active proposal on https://github.com/ethereum/eips/ and get more people involved in the discussion because it is serious.

See workflow in the top comment for what happens after acceptance.

@tomcur
Copy link

tomcur commented Jan 24, 2018

I agree with @fulldecent that tracking names is out of scope for ERC-721. For such cases one could indeed use named contracts, or perhaps track a mapping of Ethereum addresses to legal entity names inside the contract implementing ERC-721 itself.

I now also somewhat reluctantly agree with the name "deed." It seems it captures the concept better, but it also seems out of place in the common Ethereum / blockchain terminology.

@TCOA
Copy link

TCOA commented Jan 24, 2018

another thought - in making this really work for Public Records (and even many private ones), there needs to be a method to both subdivide (creating 2-x number of smaller parcels, each covered by their own deed and defined in something like an IPFS) and combine (bringing in multiple smaller parcels to make one large deed - ex: an older neighborhood being taken up by 'eminent domain' to build an airport).

These activities are often governed by various planners/engineers and require an approval from a central authority before they can be made part of the record - so, just who/how can that be tracked and authorized?

One more.....
When I go to look for property, sometimes, I am just driving around and see a spot that look appealing - maybe an unutilized corner or even just an open space out in the middle of nowhere. I then need to find out who owns that, so going to the Public Records, I can look by various means: street address (if there is one) or various types of printed land maps (which may be best suited to lookup via lat/lng or other method of pinpointing, though will usually give a record number or legal description) to find the legal owner, which then starts the negotiation process, which of course then leads to the transfer and requires record updates.

How might the community envision such 'reverse lookup' things?

I realize I'm going a bit further here - and some of these things are either best handled in another way, or perhaps put on the 'future spec' list, though I was attracted to Ethereum blockchain as a Public Records replacement system, not only recording 'vaporland'. If all these things can be brought in up front, though, how great would that be?

@tomcur
Copy link

tomcur commented Jan 24, 2018

@TCOA Those are definitely interesting use-cases. However, I think ERC-721 should focus on what it means for something to be "non-fungibly transferable." In the case of property, there would be a contract implementing ERC-721, as well as a means of transforming a single deed into multiple other deeds (subdividing) or transforming multiple deeds into a single deed (combining).

This would not be functionality specific to ERC-721, but rather something making use of the properties of ERC-721.

@TCOA
Copy link

TCOA commented Jan 24, 2018

@Beskhue thanks for clarifying. I'm still new to 'contracts' as known in blockchain though I'm starting to see just how expanded they can become.

One more 'for the record just to say it was brought up' (though from what I'm learning I think it may apply to the specific way a blockchain contract developed for a particular Public Records entity might be built) - Liens and other 'hindrances' and/or 'attachments' to a particular deed (and, of course, the process of their removal). As the type of such things vary from governing body I place them all into a heading of what we know as "dirty title" - how would you feel about this?

In normal research of the property, such 'dirt' gets brought out by a Title Company (another entity that I would happily see eliminated by the blockchain...), however, in the proposed 'exchange of NFT', there is only a chain of title, while real-world mechanisms are much more involved.

When 'dirt' is found (sometimes not even known by the seller), various processes are required to remove it and the end result is a 'clean' title - guaranteed by a Title Company as being "as clean as we can figure out from the research we did and so we believe we can say it is 'clean' so the buyer won't likely get slapped later" - the Title Company gets paid 'insurance' (which I am positive is out of the scope here) and the sale goes through.

What I'm going for here - Is there a use-case here worthy of specifying the requirement for the seller to state this is a 'clean title'? (I'm getting the feeling I know what you will say - and that then makes a business case for a 'Blockchain Title' company..... :(

@devzl
Copy link

devzl commented Jan 24, 2018

@fulldecent for the "^^^ If there is a more elegant way to explain this, I'd like to include it in the EIP." part, maybe you would want to say something in the lines of:
" A non-fungible token (or deed ) is the representation of a unique, distinct asset in a smart contract, whereas a fungible token can be interchanged with, and is similar to a token of the same type."

@fulldecent
Copy link
Contributor Author

@TCOA Your discussion and creative thinking are especially welcome here. If there is something that is incompatible with the standard then we need to decide if the standard should change. But so far you are just testing our creative thinking skills.

Subdivisions or combinations of property should probably be implemented by creating a new deed and assigning ownership of the new deed to the own owner(s). See create and destroy in the Transfer documentation.

Regarding your note about searching by parcel identifier or street name. This is a similar situation as searching by owner name. And equally compatible with this standard.

@fulldecent
Copy link
Contributor Author

Regarding encumbered titles. The liens mechanism is such that you cannot transfer a deed if liens exist. And liens come from various sources but they are ultimately recognized by the deed-recording organization.

This standard is compatible with such a process. Please see text of the transfer function. Transfers may fail (throw) for other business reasons. Two specifics cases are dictated which must throw, but no limitation exists against other situations that might throw. Therefore encumbrances can be implemented.

@fulldecent
Copy link
Contributor Author

fulldecent commented Mar 16, 2018 via email

@mudgen
Copy link
Contributor

mudgen commented Mar 16, 2018

@fulldecent Nevermind about the function overloading error , it was a bug in my code. Sorry.

@mudgen
Copy link
Contributor

mudgen commented Mar 16, 2018

What should happen if the zero address(0) is passed to the setApprovalForAll and isApprovedForAll functions? Throw? or no?

@mudgen
Copy link
Contributor

mudgen commented Mar 19, 2018

@fulldecent Is it the plan to leave unspecified what happens when the zero address is passed to either of the functions setApprovalForAll and isApprovedForAll ?

@ghost
Copy link

ghost commented Mar 19, 2018

Hi @fulldecent,

First of all, thanks for all the hard work!

Quick question on the ERC-165 identifier of the ERC-721 interface. In the draft standard it is mentioned that the ERC-165 identifier of the interface should be equal to 0x6466353c.

For some reason, I can't get our implementation to reflect that value, whereas the values for ERC721MetaData, ERC721Enumerable, and ERC721TokenReceiver appear to be correct. I've made a quick ethfiddle to demonstrate: link. Am I understanding ERC-165 wrong, or could it be that the value in the document is incorrect?

At Aethia we're looking to replace our current ownership contract with the current draft as it suits our needs a bit better. We just happened to notice this discrepancy while writing the new tests for the ownership contract.

@cbdotguru
Copy link
Contributor

The natspec for ownerOf() is off. Every function is preceded by @notice then @dev then @param except for ownerOf(). It has @param then @dev for some reason. Consider reversing those two.

@fulldecent
Copy link
Contributor Author

@HACKDOM Good find, thank you. Can you please make a pull request to correct this and mention me?

@fulldecent
Copy link
Contributor Author

fulldecent commented Mar 20, 2018

@mananz Thank you very much for reporting this. You are correct, the right interfaceID should be

0x80ac58cd // confirmed at https://ethfiddle.com/uJgB9kJMXC

The other two interface IDs are correct as is.

Would you please make a pull request to update 741.md? Also, please make an issue at https://github.com/fulldecent/su-squares-bounty and email me, I would like to pay you for this discovery!

@fulldecent
Copy link
Contributor Author

@mananz Can you please also email me more details about Aethia. I will be speaking at Explore 721 in Dallas this weekend and can talk about it.

@fulldecent
Copy link
Contributor Author

@mudgen Thank you for bringing this up. Currently the standard is silent on this item and I have added it to the OPEN ITEMS at the top here. Currently OpenZeppelin's in-progress implementation treats the zero address like any other address for setApprovalForAll.

Of course, there is no practical importance to this question. The zero address will never send transactions to a contract. However, it is a valid question. Potential ideas are to throw or to allow it or to silently fail. And lastly there is undefined behavior, which we currently have. If we don't make an active decision on this then I'll ask the audience at Explore 721 and we can make an active decision.

@nicola
Copy link

nicola commented Mar 24, 2018

Is there a possibility to go include “approveAndCall” ?

It would be nice 👍🏼

@fulldecent
Copy link
Contributor Author

@nicola Thanks for sharing.

At present, many people are using the interface and we are not considering to add required functions unless a serious problem is found with the existing.

BUT DON'T LET THAT STOP YOU. Rock on definitely include that where you need it. And if you need on the NFT side and on the wallet side then that's awesome. As other people adopt the idea too then that's when it's time to talk about updating the standard.

@nicola
Copy link

nicola commented Mar 26, 2018

It's a bit annoying that we don't have approveAndCall, since you can't really make erc721 "programmable", unless you ask the user to do two requests :(

I really need to do: approve the cryptokitty for the auction and create an auction!

@fulldecent
Copy link
Contributor Author

First, of course. CryptoKitties is already deployed. Any change to the standard will not affect that specific application.

If you are writing your own application that you can definitely use approveAndCall functionality. You could implement this on the ERC721 side or on the wallet side. At present, no change is likely to happen in 721. But if you would like to discuss further, please mention me on your repository when you have a working prototype.

@MoMannn
Copy link
Contributor

MoMannn commented Mar 28, 2018

@fulldecent

I have a question regarding transfers. I want to make NFT where transfers are not payable. For that I need to change the interface to remove payable if I want to implement it as MyContract is ERC721. Otherwise I get an error (TypeError: Overriding function changes state mutability from "payable" to "nonpayable".).

If I change the interface and calculate interfaceID for it (for EIP165) will it be different than expected?

Or should the whole implementation of MyContract is ERC721 be different?

@Arachnid
Copy link
Contributor

@MoMannn The 'payable' modifier is not part of the ABI, so you can add or remove it without affecting anything else (including the interface ID).

@fulldecent
Copy link
Contributor Author

@MoMannn Please see the caveats section. You are welcome to update the interface from payable to default non-payable by editing the interface.

This is a known Solidity issue cited in the standard. In the future (version 0.4.22) that TypeError will disappear in such a situation.

Also (as noted in the standard) when this new Solidity version is released, I intend to edit the EIP and "rebase" it against the new Solidity.

@AnAllergyToAnalogy
Copy link
Contributor

I think the mutability for the name() and symbol() functions of the ERC721Metadata extension should be changed.

They're both set to pure, but this would mean the name and symbol would have to be hard-coded into the contract functions, rather than stored in a variable (which is presumably set in the constructor).

The mutability guarantee rules allow for strengthening, but not weakening, so I think they should be changed to view in the interface.

@fulldecent
Copy link
Contributor Author

@AnAllergyToAnalogy This is discussed in #1072 and I support your change.

@fulldecent
Copy link
Contributor Author

@paulbarclay @dekz We have reviewed on an invalid assumption when deciding which event topics should be indexed. We had assumed that indexed topics are not recoverable in plain text. However, according to the Su Squares actual deployed contract, we can see that the addresses are viewable.

Please scroll down to: 0x23de16e75c4b8...

https://etherscan.io/address/0x6731560e455537c9f088ea02a47a0ecfa28a9231#events

You can see the _from and _to which are indexed.

I suppose that tokenId would be recoverable as well if it were indexed. But I would like to see concrete evidence of that. (XYZ123)


Of course there may be a question of whether the standard should be edited to require all contracts to use indexed tokenId. My gut reaction is "no" right now because producer contracts (products) could easily implement this if they want and they will still be compatible with consumer contracts (wallets) that are expecting a not indexed field. Prior art is that Crypto Kitties does NOT have any indexed fields. Source: https://etherscan.io/tx/0x50c88da2694e6862cbff087f73c88f4cee86d02edae20431e8920b54bfd94542#eventlog

Thank you to @gnarco for bringing this up. #721 (comment)

@fulldecent
Copy link
Contributor Author

CHANGE ACCEPTED TO 721 #1072

@mudgen
Copy link
Contributor

mudgen commented May 29, 2018

@fulldecent Is the standard going to be updated with indexed tokenId for the Transfer and Approval events? I think it makes sense.

/// @dev This emits when the approved address for an NFT 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 NFT (if any) is reset to none.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this means, Approval event needs to be emitted too upon transfer or does this means the Transfer event is sufficient ? The text here could be clearer maybe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Approval event is not emitted on transfer.

@fulldecent
Copy link
Contributor Author

Here is a minor implementation note which is related to the Constantinople EVM (which went live last year on the Ethereum mainnet but is not currently live on some EVM derivatives, like Wanchain).

This relates to safeTransferFrom which needs to determine if the token recipient is a contract. The exact requirement in the ERC-721 specification is "code size > 0".

Previously this was implemented using EXTCODESIZE. And now, the major ERC-721 implementations (0xcert implementation and OpenZeppelin implementation) have switched to the newly available EXTCODEHASH. The only difference in behavior is handling when the token recipient is a smart contract that is currently in the process of initializing (in Yellow Paper speak, the "initialization code is executing").

If you are not sending tokens using safeTransferFrom to a contract while it is initializing then this does not concern you.

If you are interested in nitty-gritty test cases, track that here ➡️ nibbstack/erc721#235

To discuss this ➡️ #2401

All the hard work on this is done by: @MoMannn & @frangio. I'm just here to report.


Since this change is technically WITHIN the original specification the new implementations are compliant with the standard. I do not think it is necessary to amend the EIP or to make a new EIP. And perhaps this small GitHub comment posted here, which is the "discussion-to" URL for ERC-721 is all that needs to be said in the EIP standards context. But of course, to discuss the technical details further please see the links above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.