diff --git a/EIPS/eip-2381.md b/EIPS/eip-2381.md new file mode 100644 index 00000000000000..44f881f70d23a0 --- /dev/null +++ b/EIPS/eip-2381.md @@ -0,0 +1,145 @@ +--- +eip: 2381 +title: ENS support for EIP-721 TokenID +author: Oisin Kyne (@OisinKyne) +discussions-to: https://github.com/ethereum/EIPs/issues/2381 +status: Draft +type: Standards Track +category: ERC +created: 2019-11-15 +requires: 137, 165, 721 +--- + +## Simple Summary + +This EIP makes [EIP-721](https://eips.ethereum.org/EIPS/eip-721) Non-Fungible Tokens addressable by the Ethereum Name Service ([EIP-137](https://eips.ethereum.org/EIPS/eip-137)). + +## Abstract + +A single deployed EIP-721 contract can contain multiple non-fungible tokens. Tokens are addressed by an unsigned 256bit integer, `tokenID`. This EIP describes an extra getter and setter, `tokenID()` and `setTokenID()`, that are included in an ENS resolver contract, to allow for ENS names to resolve to a specific non fungible token within a given EIP-721 contract. + +This allows ENS domains like [`bugcat.cryptokitties.eth`](https://www.cryptokitties.co/kitty/101) and [`dragon.cryptokitties.eth`](https://www.cryptokitties.co/kitty/896775) to resolve to both the CryptoKitties contract address and the tokenID of the non-fungible kitty within it by leveraging [EIP-721](./eip-721.md)'s `tokenURI` method with the token ID retrieved from ENS. + +## Motivation + +It makes sense to resolve an ENS name to just a contract address for fungible tokens such as [EIP-20](https://eips.ethereum.org/EIPS/eip-20), as each token in the contract is indistinguishable from another. However, for non-fungible tokens; pointing to the contract address alone is not enough, as tokens within the contract are meant to be unique and distinguishable. Non-fungibles might have different valuations, different artwork, might grant the holder different rights or rewards etc. + +Giving NFTs names makes them more real to the non Ethereum enthusiast that doesn't understand what a contract address or tokenID is. A domain name in the existing web 2.0 world is already widely understood to be a finite resource, and as such, would help convey the scarcity that is a non-Fungible token to new users. + +This change might also to an extent, decentralise access to non-fungibles on Ethereum. Currently, the mapping for a human readable name to an NFT is kept off chain, typically on the platform of the NFT issuer. However, if the community moved towards naming NFTs on chain, using ENS, this would allow any client that supports ENS resolution to resolve an ENS name to an NFT, without querying the issuer's off-chain metadata. + +Finally, this EIP aims to address fraud in the NFT space. Many famous NFTs are being saved and reuploaded by malicious scammers with the intention of defrauding investors into purchasing rip offs of a real NFT. With ENS NFTs, a digital artist can use their well-known ENS name to confer legitimacy to the canonical NFT representation of their artwork to distinguish it from the imposters. + +## Specification + +The ENS Resolver Profile specified by this EIP adds two functions to a deployed resolver contract: + +``` +function tokenID(bytes32 node) public view returns(uint256); +function setTokenID(bytes32 node, uint256 token); +``` + +You can evaluate whether a given ENS resolver supports this EIP by using the [EIP-165](./eip-165.md) standard `supportsInterface(bytes4)`. + +The interface identifier for this interface is `0x4b23de55`. + +## Rationale + +The design of this smart contract is a modified replica of the [existing contenthash resolver profile](https://github.com/ensdomains/resolvers/blob/master/contracts/profiles/ContentHashResolver.sol). + +The decision to use Contract Address and tokenID as a pair to uniquely identify EIP-721 NFTs is lifted directly from the [EIP-721 Rationale](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md#rationale) quoted here: + +> Every NFT is identified by a unique uint256 ID inside the [EIP-721](./eip-721.md) smart contract. This identifying number **SHALL NOT** change for the life of the contract. The pair (contract address, uint256 tokenId) will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. + +## Backwards Compatibility + +No backwards compatibility issues arise. Users wanting to address NFTs will have to deploy new ENS resolvers themselves, or use the provided PublicResolver below. + +## Implementation + +The following code serves as a sample implementation of this `tokenId` resolver: + +```solidity +pragma solidity ^0.5.8; + +import "../ResolverBase.sol"; + +contract TokenIDResolver is ResolverBase { + bytes4 constant private TOKENID_INTERFACE_ID = 0x4b23de55; + + event TokenIDChanged(bytes32 indexed node, uint256 tokenID); + + mapping(bytes32=>uint256) _tokenIDs; + + /** + * Returns the tokenID associated with an ENS node. + * @param node The ENS node to query. + * @return The associated tokenID. + */ + function tokenID(bytes32 node) public view returns(uint256) { + return _tokenIDs[node]; + } + + /** + * Sets the tokenID associated with an ENS node. + * May only be called by those authorised for this node in the ENS registry. + * @param node The node to update. + * @param token The tokenID to set + */ + function setTokenID(bytes32 node, uint256 token) public authorised(node) { + emit TokenIDChanged(node, token); + _tokenIDs[node] = token; + } + + function supportsInterface(bytes4 interfaceID) public pure returns(bool) { + return interfaceID == TOKENID_INTERFACE_ID || super.supportsInterface(interfaceID); + } +} +``` + +A fork of the [ens/resolvers](https://github.com/ensdomains/resolvers) repo with the change is available, [here](https://github.com/OisinKyne/resolvers/blob/master/contracts/profiles/TokenIDResolver.sol) + +The ENS public resolver contract, with this new added profile, has been deployed and verified on etherscan on the following chains: + +- Rinkeby Testnet: [0x9d5dd30b5d77665f0c2f082cccc077d349ba1afc](https://rinkeby.etherscan.io/address/0x9d5dd30b5d77665f0c2f082cccc077d349ba1afc) +- Ropsten Testnet: [0xf39f73b0c748d284dcea3f0da8bbdefa7a789c6b](https://ropsten.etherscan.io/address/0xf39f73b0c748d284dcea3f0da8bbdefa7a789c6b) +- Goerli Testnet: [0x2618f1ed8590cb750489ce5de0d1c05d8375bbdf](https://goerli.etherscan.io/address/0x2618f1ed8590cb750489ce5de0d1c05d8375bbdf) +- Mainnet: [0xb2eef9d0235a339179a7e177e818439dcca9d76e](https://etherscan.io/address/0xb2eef9d0235a339179a7e177e818439dcca9d76e) + +To test this, I have set [this](https://etherscan.io/address/0x888ab947cb7135dc25d4936e9a49b4e2bcdea467) new resolver contract on mainnet to resolve [`devcon5.oisin.eth`](https://etherscan.io/enslookup?q=devcon5.oisin.eth) to my Devcon Ticket Non-Fugible. + +The address it resolves to is: +[0x22cc8b3666e926bcbf58cb726143b2b044c80a0c](https://etherscan.io/token/0x22cc8b3666e926bcbf58cb726143b2b044c80a0c), which is the [contract address](https://etherscan.io/token/0x22cc8b3666e926bcbf58cb726143b2b044c80a0c) of all 91 tokens issued. +And the `tokenID()` function returns: +[10798952828109286844408842969080375883371044426718767566816061252817119618319](https://etherscan.io/token/0x22cc8b3666e926bcbf58cb726143b2b044c80a0c?a=10798952828109286844408842969080375883371044426718767566816061252817119618319) when you supply it with a node of `0x0532eb949a568331eccfc0b70427e8aa96bcbfcc747607711bd04275323a1b49` (which is the namehash of `devcon5.oisin.eth`), which is _my_ Non Fungible within that contract, hopefully illustrating this EIPs usefulness. :) + +### How to check if an ENS name implements this EIP? + +To make sure everything is as expected, one should follow this procedure: + +- Given an ENS name, first look up its resolver contract from the [ENS registry](https://docs.ens.domains/contract-api-reference/ens) by calling `resolver(bytes32 node)` on the registry contract. +- Given an ENS name and its resolver contract address, look up its `addr` field, which returns an address if one is set. +- Check if this address is an **EIP-721 contract**, by using EIP-165's `supportsInterface` method. (EIP-721 interface ID is `0x80ac58cd`). +- If this address is an EIP-721 contract, now you should check whether this name is pointing at the entire NFT contract, or whether it is pointing at a specific NFT within it. + - First, check if the **resolver contract** supports EIP-2381, by using EIP-165's `supportsInterface` method. (EIP-2381 interface ID is `0x4b23de55`). + - If the resolver contract *does not support* EIP-2381, you can safely assume this name does not address a specific NFT within the EIP-721 contract. + - If the resolver contract *does support* EIP-2381, you should call the function: `tokenID(bytes32 node)` on the resolver contract. + - If the `tokenID` function returns 0, you can assume that this name is not addressing a specific NFT. (0 is not a valid tokenID in the EIP-721 standard). + - If the `tokenID` function returns a non-zero value, this name is addressing a specific NFT within the contract. +- If a `tokenID` is set on the resolver contract, it is advised that you verify that this `tokenID` exists within the EIP-721 contract, and hasn't been burned or never been minted, for example. + + +## Security Considerations + +There is no centralised party involved in this EIP, all contracts are verified on etherscan and all contracts can be redeployed trustlessly if desired. + +The main security consideration to note with this EIP is *the potential for fraud*. + +The person that controls the ENS name may not be the same person that issues or owns the NFT it points at. This may not be apparent to non-technical users. This could potentially cause confusion by convincing a user to purchase the ENS name (which is itself an NFT) rather than the NFT addressed by the ENS name. + +Similarly, the ENS name owner could update the name to point at a new NFT token at any point in time. This EIP does not propose a form of reverse-resolution to allow for a canonical ENS name for an NFT and therefore should not be used as a permanent record of a particular NFT and instead should just be used as a UX or informational improvement to an NFT. + + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).