{"action":"created","issue":{"url":"https://api.github.com/repos/ethereum/EIPs/issues/821","repository_url":"https://api.github.com/repos/ethereum/EIPs","labels_url":"https://api.github.com/repos/ethereum/EIPs/issues/821/labels{/name}","comments_url":"https://api.github.com/repos/ethereum/EIPs/issues/821/comments","events_url":"https://api.github.com/repos/ethereum/EIPs/issues/821/events","html_url":"https://github.com/ethereum/EIPs/issues/821","id":286291948,"number":821,"title":"EIP 821: Distinguishable Assets Registry (Contract for NFTs)","user":{"login":"eordano","id":42750,"avatar_url":"https://avatars2.githubusercontent.com/u/42750?v=4","gravatar_id":"","url":"https://api.github.com/users/eordano","html_url":"https://github.com/eordano","followers_url":"https://api.github.com/users/eordano/followers","following_url":"https://api.github.com/users/eordano/following{/other_user}","gists_url":"https://api.github.com/users/eordano/gists{/gist_id}","starred_url":"https://api.github.com/users/eordano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eordano/subscriptions","organizations_url":"https://api.github.com/users/eordano/orgs","repos_url":"https://api.github.com/users/eordano/repos","events_url":"https://api.github.com/users/eordano/events{/privacy}","received_events_url":"https://api.github.com/users/eordano/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":56,"created_at":"2018-01-05T13:42:20Z","updated_at":"2018-02-17T00:54:15Z","closed_at":null,"author_association":"NONE","body":"    EIP: 821\r\n    Title: Distinguishable Assets Registry\r\n    Author: Esteban Ordano <esteban@decentraland.org>\r\n    Type: Standard Track\r\n    Category: ERC\r\n    Status: Draft\r\n    Created: 2018-01-05\r\n    Updated: 2018-02-04\r\n\r\n## Summary\r\n\r\nA Distinguishable Assets Registry is a contract that tracks ownership of, and information about a set of assets.\r\n\r\nSee https://github.com/decentraland/erc821 for a reference implementation.\r\n\r\nSee the \"Revisions\" section for a history of this ERC.\r\n\r\n## Abstract\r\n\r\nTracking 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.\r\n\r\n## Introduction\r\n\r\nThe 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.\r\n\r\nSee also: ERC #721, ERC #20, ERC #223, and ERC #777.\r\n\r\nThe 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.\r\n\r\n## Specification\r\n\r\nA 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.\r\n\r\nDARs 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.\r\n\r\nNFTs SHOULD be referenced by a URI that follows this schema:\r\n\r\n    nft://<chain's common name>/<DAR's address>/<NFT's ID>\r\n\r\nThe 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.\r\n\r\nSome common names for Ethereum blockchains are:\r\n\r\n* `ethereum`, `livenet`, or `mainnet`\r\n* `ropsten`, `testnet`\r\n* `kovan`\r\n* `rinkeby`\r\n\r\nSome examples of NFT URIs follow:\r\n\r\n* nft://ethereum/0xF87E31492Faf9A91B02Ee0dEAAd50d51d56D5d4d/0\r\n* nft://ropsten/0xF87E31492Faf9A91B02Ee0dEAAd50d51d56D5d4d/0xfaa5be24e996feadf4c96b905af2c77c456e2debd075bab4d8fd5f70f209de44\r\n\r\nEvery NFT MUST have a `owner` address associated with it. NFTs associated with the null address are assumed non-existent or destroyed.\r\n\r\nAn owner MAY assign one or multiple `operator` addresses. These addresses will be able to transfer any asset of the owner.\r\n\r\nDARs MAY trigger `Update` events, signaling that associated data has changed.\r\n\r\nDARs MUST trigger `Transfer` events every time a NFT's `owner` changes. This might happen under three circumstances:\r\n\r\n* A NFT is created. In this case, the `from` value of the `Transfer` event MUST be the zero address.\r\n* A NFT is transferred to a different owner.\r\n* A NFT is destroyed. In this case, the `to` value of the `Transfer` event MUST be the zero address.\r\n\r\n`Transfer` events MUST 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).\r\n\r\n#### Associated Metadata\r\n\r\n##### Version 0\r\n\r\nAny NFT MAY have a `string` value associated with it, named `data`, or `metadata`. This associated data MAY contain a URL to fetch information related to the NFT. This URL SHOULD point to a folder with the following subpaths:\r\n\r\n* `name`\r\n* `image`\r\n* `description`\r\n\r\nThis is intended to provide any NFT-capable dApp with enough information to display the NFT in its UI. For example, if the `data` associated with an NFT is `https://example.com/my_asset`, it's expected that `https://example.com/my_asset/name` contains a short file with the name of the asset, and `https://example.com/my_asset/image` a ICO or PNG image that represents the asset.\r\n\r\n##### Version 1\r\n\r\nWhen the associated data's first bit is on, the first byte should be interpreted as the two's complement of the version of the metadata. Version 1 of the protocol (`0xff`) allows the NFT's name and a short description to be stored on-chain, by using a solidity-style-packed structure with the following fields:\r\n\r\n```\r\nstruct Metadata {\r\n  uint8 versionBits;\r\n  string name;\r\n  string description;\r\n  string url;\r\n}\r\n```\r\n\r\nBy doing this, NFT wallets are allowed to display information associated with the NFT independently of any additional HTTPS/IPFS requests, at the cost of some extra gas spenditure.\r\n\r\n### DAR global methods\r\n\r\n* `name`: `string`\r\n* `symbol`: `string`\r\n* `description`: `string`\r\n* `decimals`: `uint256`, always zero\r\n* `totalSupply`: `uint256`\r\n\r\nThese properties follow the standards of #20 and introduces a `description`. These are all `view` functions.\r\n\r\n#### `isERC821():bool`\r\n\r\nThis method returns `true`. This method MUST NOT throw.\r\n\r\n### NFT getter methods\r\n\r\n#### `exists(uint256 assetId):bool`\r\n\r\nThis method returns a boolean value, `true` if the asset identified with the given `assetId` exists under this DAR. This method MUST NOT throw.\r\n\r\n#### `ownerOf(uint256 assetId):address`\r\n\r\nThis method returns the `address` of the owner of the NFT. This method MUST NOT throw. If the `assetId` does not exist, the return value MUST be the null address.\r\n\r\n#### `safeOwnerOf(uint256 assetId):address`\r\n\r\nThis method returns the `address` of the owner of the NFT. This method MUST throw if the `assetId` does not exist.\r\n\r\n#### `assetData(uint256 assetId):string`\r\n\r\nThis method returns data associated with the NFT. This method MUST NOT throw. This method MAY return an empty string if the NFT has no associated data with it. This method MUST return an empty string if the NFT does not exist.\r\n\r\n#### `safeAssetData(uint256 assetId):string`\r\n\r\nThis method returns data associated with the NFT. This method MUST throw if the NFT associated with `assetId` does not exist. This method MAY return an empty string if the NFT has no associated data with it. \r\n\r\n### Owner-centric getter methods\r\n\r\n#### `assetCount(address owner):uint256`\r\n\r\nThis method returns the amount of NFTs held by the `owner` address under this DAR. This method MUST not throw.\r\n\r\n#### `balanceOf(address owner):uint256`\r\n\r\nAlias of `assetCount` so older ERC20 wallets can display a balance.\r\n\r\n#### `assetByIndex(address owner, uint256 index):uint256`\r\n\r\nThis method returns the ID of the `index`th 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.\r\n\r\nThis method MUST throw if `assetCount(owner) >= index`. This method MUST throw if `index >= 2^128`.\r\n\r\nThe DAR MAY change the order assigned to any NFT held by a particular address.\r\n\r\nThis 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.\r\n\r\n#### `assetsOf(address owner):uint256[]`\r\n\r\nThis method returns an array of IDs of the NFTs held by `owner`. This method MUST NOT throw.\r\n\r\n### Operator getters\r\n\r\n#### `isOperatorAuthorizedBy(address operator, address owner):bool`\r\n\r\nThis method returns `true` if `owner` has called the method `authorizeOperator` with parameters `(operator, true)` and has not called `authorizeOperator(operator, false)` afterwards. This method returns `false` otherwise.\r\n\r\nThis method MUST return `true` if `operator == owner`.\r\n\r\nThis method MUST NOT throw.\r\n\r\n#### `isApprovedFor(address operator, uint256 assetId):bool`\r\n\r\nThis 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.\r\n\r\nThis method MUST return `true` if `operator == owner`.\r\n\r\nThis method MUST NOT throw.\r\n\r\n### Transfers\r\n\r\n#### `transfer(address to, uint256 assetId, bytes userData, bytes operatorData)`\r\n\r\nTransfers holding of the NFT referenced by `assetId` from `ownerOf(assetId)` to the address `to`.\r\n\r\n`to` MUST NOT be the zero address. If `to` is the zero address, the call MUST throw.\r\n\r\n`to` MUST NOT be `ownerOf(assetId)`. If this condition is met, the call MUST throw.\r\n\r\n`isOperatorAuthorizedBy(msg.sender, ownerOf(assetId))` MUST return true as a precondition. \r\n\r\nThis means that the `msg.sender` MUST be `ownerOf(assetId)` or an authorized operator.\r\n\r\nIf the NFT referenced by `assetId` does not exist, then the call MUST throw.\r\n\r\nIf there was any single authorized operator (see the `approve()` method below), this authorization MUST be cleared.\r\n\r\nIf the call doesn't throw, it triggers the event `Transfer` with the following parameters:\r\n\r\n* from: value of `ownerOf(assetId)` before the call\r\n* to: the `to` argument\r\n* assetId: the `assetId` argument\r\n* operator: `msg.sender`\r\n* userData: the `userData` argument\r\n* operatorData: the `operatorData` argument\r\n\r\nIf `to` is a contract's address, this call MUST verify that the contract can receive the tokens. Two methods are proposed for this.\r\n\r\n#### Implementation of `IAssetHolder`\r\n\r\nIf doing a lookup for the `manager` of the DAR registry's address for the interface `erc821` returns a value different from zero, see the next section.\r\n\r\nIf the receiving contract has implemented `IAssetHolder` as described in the section `Events`, then the method `onAssetReceived` MUST be invoked with the corresponding information, **after** the NFT has been transferred.\r\n\r\n#### ERC820 Registration\r\n\r\nIf doing a lookup for the `manager` of the DAR registry's address for the interface `erc821` returns zero, the transfer is managed as in the previous section.\r\n\r\nIf doing a lookup for the `erc821` interface of the DAR registry's address returns a value different from the same DAR registry's address, then the DAR contract should be considered abandoned and the transaction should fail.\r\n\r\nIf `to` is a contract, and an ERC820 lookup for `IAssetHolder` **does not** return an address, this call MUST throw.\r\n\r\nIndependently of whether `to` is a contract or account, when an ERC820 lookup for `IAssetHolder` for the address returns a non-zero value, the transfer must be done to the registered manager, unless `msg.sender` is the same manager.\r\n\r\n<table>\r\n  <tr>\r\n    <th rowspan=\"2\">DAR registration on ERC820 Contract</th>\r\n    <th colspan=\"3\"><code>to</code> address<br></th>\r\n    <th rowspan=\"2\"><code>msg.sender</code></th>\r\n    <th rowspan=\"2\"><code>transfer</code> result</th>\r\n  </tr>\r\n  <tr>\r\n    <th>type</th>\r\n    <th>Implements <tt>IAssetHolder</tt></th>\r\n    <th>Has ERC820 <tt>IAssetHolder</tt> Manager</th>\r\n  </tr>\r\n  <tr>\r\n    <td rowspan=\"4\">No registration of <tt>erc821</tt> interface</td>\r\n    <td colspan=\"3\">Account</td>\r\n    <td rowspan=\"4\">*</td>\r\n    <td>Accept</td>\r\n  </tr>\r\n  <tr>\r\n    <td rowspan=\"3\">Contract</td>\r\n    <td>Yes</td>\r\n    <td>No</td>\r\n    <td>Accept</td>\r\n  </tr>\r\n  <tr>\r\n    <td>No</td>\r\n    <td>No</td>\r\n    <td>⛔️Throw</td>\r\n  </tr>\r\n  <tr>\r\n    <td>No</td>\r\n    <td>Yes</td>\r\n    <td>⛔️Throw</td>\r\n  </tr>\r\n  <tr>\r\n    <td rowspan=\"7\">Registered itself for <tt>erc821</tt></td>\r\n    <td colspan=\"2\" rowspan=\"3\">Account</td>\r\n    <td>No</td>\r\n    <td>*</td>\r\n    <td>Accept</td>\r\n  </tr>\r\n  <tr>\r\n    <td>Yes</td>\r\n    <td>Manager</td>\r\n    <td>Accept</td>\r\n  </tr>\r\n  <tr>\r\n    <td>Yes</td>\r\n    <td>*</td>\r\n    <td>Accept, send to manager</td>\r\n  </tr>\r\n  <tr>\r\n    <td rowspan=\"4\">Contract</td>\r\n    <td>Yes</td>\r\n    <td>No</td>\r\n    <td rowspan=\"2\">*</td>\r\n    <td>Accept</td>\r\n  </tr>\r\n  <tr>\r\n    <td>No</td>\r\n    <td>No</td>\r\n    <td>⛔️Throw</td>\r\n  </tr>\r\n  <tr>\r\n    <td>*</td>\r\n    <td>Yes</td>\r\n    <td>Manager</td>\r\n    <td>Accept</td>\r\n  </tr>\r\n  <tr>\r\n    <td>*</td>\r\n    <td>Yes</td>\r\n    <td>*</td>\r\n    <td>Accept, send to manager</td>\r\n  </tr>\r\n</table>\r\n\r\n#### `transfer(address to, uint256 assetId, bytes userData)`\r\n\r\nShorthand method that MUST be equivalent to calling `transfer(to, assetId, userData, EMPTY_BYTES)`.\r\n\r\n#### `transfer(address to, uint256 assetId)`\r\n\r\nShorthand method that MUST be equivalent to calling `transfer(to, assetId, EMPTY_BYTES, EMPTY_BYTES)`.\r\n\r\n### Authorization\r\n\r\n#### `authorizeOperator(address operator, bool authorized)`\r\n\r\nIf `authorized` is `true`, allows `operator` to `transfer` any NFT held by `msg.sender`.\r\n\r\nThis 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.\r\n\r\nThis method MUST throw if `msg.sender == operator`.\r\n\r\nThis method MUST trigger an `AuthorizeOperator` event if it doesn't throw.\r\n\r\n#### `approve(address operator, uint256 assetId)`\r\n\r\nAllow `operator` to `transfer` an asset without delegating full access to all assets.\r\n\r\nThis method MUST trigger an `Approve` event if it doesn't throw.\r\n\r\n### Events\r\n\r\n```\r\ninterface AssetRegistryEvents {\r\n  event Transfer(\r\n    address indexed from,\r\n    address indexed to,\r\n    uint256 indexed assetId,\r\n    address operator,\r\n    bytes userData,\r\n    bytes operatorData\r\n  );\r\n  event Update(\r\n    uint256 indexed assetId,\r\n    address indexed holder,\r\n    address indexed operator,\r\n    string data\r\n  );\r\n  event AuthorizeOperator(\r\n    address indexed operator,\r\n    address indexed holder,\r\n    bool authorized\r\n  );\r\n  event Approve(\r\n    address indexed owner,\r\n    address indexed operator,\r\n    uint256 indexed assetId\r\n  );\r\n}\r\n```\r\n### Interfaces\r\n```\r\ninterface IAssetRegistry {\r\n  function name() public view returns (string);\r\n  function symbol() public view returns (string);\r\n  function description() public view returns (string);\r\n  function totalSupply() public view returns (uint256);\r\n  function decimals() public view returns (uint256);\r\n\r\n  function isERC821() public view returns (bool);\r\n\r\n  function exists(uint256 assetId) public view returns (bool);\r\n  function holderOf(uint256 assetId) public view returns (address);\r\n  function ownerOf(uint256 assetId) public view returns (address);\r\n\r\n  function safeHolderOf(uint256 assetId) public view returns (address);\r\n  function safeOwnerOf(uint256 assetId) public view returns (address);\r\n\r\n  function assetData(uint256 assetId) public view returns (string);\r\n  function safeAssetData(uint256 assetId) public view returns (string);\r\n\r\n  function assetCount(address holder) public view returns (uint256);\r\n  function balaceOf(address holder) public view returns (uint256);\r\n\r\n  function assetByIndex(address holder, uint256 index) public view returns (uint256);\r\n  function assetsOf(address holder) external view returns (uint256[]);\r\n\r\n  function transfer(address to, uint256 assetId) public;\r\n  function transfer(address to, uint256 assetId, bytes userData) public;\r\n  function transfer(address to, uint256 assetId, bytes userData, bytes operatorData) public;\r\n\r\n  function authorizeOperator(address operator, bool authorized) public;\r\n  function approve(address operator, uint256 assetId) public;\r\n\r\n  function isOperatorAuthorizedBy(address operator, address assetHolder) public view returns (bool);\r\n  function isApprovedFor(address operator, uint256 assetId) public view returns (bool);\r\n  function approvedFor(uint256 assetId) public view returns (address);\r\n\r\n}\r\n```\r\n```\r\ninterface IAssetHolder {\r\n  function onAssetReceived(\r\n    /* address _assetRegistry == msg.sender */\r\n    uint256 _assetId,\r\n    address _previousOwner,\r\n    address _currentOwner,\r\n    bytes   _userData,\r\n    address _operator,\r\n    bytes   _operatorData\r\n  ) public;\r\n}\r\n```\r\n\r\n## Implementation\r\n\r\nhttps://github.com/decentraland/erc821\r\n\r\n## Design goals\r\n\r\n### Staying up to date with the latest standards\r\n\r\nStandards like ERC #820 and ERC #777 represent the latest best practices on token standards. #777 introduces better vocabulary surrounding the `approve` method and accommodates the fact that successful contracts using `approve` end up asking the user to \"approve any transfer\", by calling `approve(2^256-1)`.\r\n\r\nERC #820 is the latest iteration of #672, a standard that allows contracts to upgrade in a non-disruptive way, but without enforcing ENS to achieve this goal.\r\n\r\n### Transfer known patterns if they apply, follow the trends that correct anti-patterns\r\n\r\nThe ERC #20 introduced a new vocabulary (`owner`, `approve`, `supply`, `transfer`, `transferFrom`) and we should try to reuse as much  of that vocabulary and those patterns as possible , but only when they apply. For instance, `transferFrom` doesn't make sense for a NFT; it's intended to allow an approved agent (after `approve`) to move someone else's token, but with an NFT as described in this standard, the \"`owner`\" is known beforehand.\r\n\r\nERC #777 introduces the name `operator` to replace the functionality previously provided by `approve`. We believe this is a good change, given that widely used contracts (EtherDelta, 0x) have adopted the use of a call to `approve(2^256-1)`.\r\n\r\n### Provide a reference implementation\r\n\r\n#20 failed to provide a reference implementation right away -- which made for the standard to be written and rewritten on multiple occasions. We provide a reference implementation from day one, along with guidance on how to extend the contract to suit the policy according to each NFT class.\r\n\r\nOur implementation tries to follow the UNIX principle of providing mechanisms, not policy. That's why we included functions to create, update, and destroy tokens, but marked them as `internal`.\r\n\r\n### Reduce gas costs\r\n\r\nWhenever possible, the gas costs should be kept at a minimum.\r\n\r\n## Revisions\r\n\r\n* 2018/02/04: Make most of the interface ERC721-compatible and updated the implementation to match spec\r\n* 2018/02/03: Add `approve` to approve individual assets to be transferred\r\n* 2018/01/27: Add exception for `msg.sender` being the manager\r\n* 2018/01/27: Add table for `ERC820` and `IAssetHolder` interface check\r\n* 2018/01/27: Change `IAssetOwner` for `IAssetHolder` to reflect that another contract might hold tokens for one\r\n* 2018/01/27: Errata on `transfer` text\r\n* 2018/01/26: Alias \"balanceOf\" to \"assetCount\" for ERC20 compatibility\r\n* 2018/01/26: Add `decimals` for more ERC20 compatibility\r\n* 2018/01/26: Propose more metadata to be stored on-chain.\r\n* 2018/01/26: Make EIP820 compatibility optional to receive tokens if `onAssetReceived(...)` is implemented.\r\n* 2018/01/26: Add `isERC821` flag to detect support.\r\n* 2018/01/26: Revert `holder` to `owner`.\r\n* 2018/01/18: `transfer`: `to` MUST NOT be `holderOf(assetId)`\r\n* 2018/01/17: Added `safeAssetData` method\r\n* 2018/01/17: Clarification to `transfer`: it MUST throw if the asset does not exist.\r\n* 2018/01/17: Published first version of the specification\r\n* 2018/01/16: Published implementation\r\n* 2018/01/05: Initial draft\r\n\r\n## Copyright\r\n\r\nCopyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)."},"comment":{"url":"https://api.github.com/repos/ethereum/EIPs/issues/comments/366400259","html_url":"https://github.com/ethereum/EIPs/issues/821#issuecomment-366400259","issue_url":"https://api.github.com/repos/ethereum/EIPs/issues/821","id":366400259,"user":{"login":"nicola","id":1424850,"avatar_url":"https://avatars1.githubusercontent.com/u/1424850?v=4","gravatar_id":"","url":"https://api.github.com/users/nicola","html_url":"https://github.com/nicola","followers_url":"https://api.github.com/users/nicola/followers","following_url":"https://api.github.com/users/nicola/following{/other_user}","gists_url":"https://api.github.com/users/nicola/gists{/gist_id}","starred_url":"https://api.github.com/users/nicola/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nicola/subscriptions","organizations_url":"https://api.github.com/users/nicola/orgs","repos_url":"https://api.github.com/users/nicola/repos","events_url":"https://api.github.com/users/nicola/events{/privacy}","received_events_url":"https://api.github.com/users/nicola/received_events","type":"User","site_admin":false},"created_at":"2018-02-17T00:54:15Z","updated_at":"2018-02-17T00:54:15Z","author_association":"NONE","body":"I wonder if multiformats can play a role in here (ping @whyrusleeping)"}}
{"actor":{"display_login":"nicola"}}