Skip to content

Instantly share code, notes, and snippets.

@sahilrajput03
Last active November 14, 2022 04:56
Show Gist options
  • Select an option

  • Save sahilrajput03/3d0536338bf52c2ecd0c7f1177b2df9d to your computer and use it in GitHub Desktop.

Select an option

Save sahilrajput03/3d0536338bf52c2ecd0c7f1177b2df9d to your computer and use it in GitHub Desktop.
commentary ERC721.md

Commentary on ERC721

Source: ethereum.org

Implementation on Openzeppelin:

Every ERC-721 compliant contract must implement the ERC721 and ERC165 interfaces:

  • Methods:
// ERC-165 identifier for this interface is 0x80ac58cd
interface ERC721 /* is ERC165 */ {...}
**Contents of above contract as below:**


- balanceOf(): Returns the count of all NFTs assigned to an owner. ALSO: `function balanceOf(address _owner) external view returns (uint256)`
    - NFTs assigned to the zero address are considered invalid, and this function throws for queries about the zero address.

- ownerOf(): Returns owner of an NFT. ALSO: `function ownerOf(uint256 _tokenId) external view returns (address)`
  - NFTs assigned to zero address are considered invalid, and queries about them do throw. `_tokenId` is the identifier for an NFT.
  
- safeTransferFrom(): Transfers the ownership of an NFT from one address to another address. ALSO: `function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable`
  - Additional `data` is data with no specified format, sent in call to `_to` i.e., when transfer is complete, the 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,address,uint256,bytes)"))`.
  - Throws if
    - `_from` is not the current owner
    - `_to` is the zero address
    - `_tokenId` is not a valid NFT
    - `_to` is the zero address.
    - `_tokenId` is not a valid NFT.
  - Throws unless `msg.sender`
    - is the current owner
    - or an authorized operator
    - or the approved address for this NFT.

- safeTransferFrom(): Transfers the ownership of an NFT from one address to another address. Identical to the previous function but this function just sets data to "". ALSO: `function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable`

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

- approve(): Change or reaffirm the approved address for an NFT. Param `_approved` is the new approved NFT controller. ALSO: `function approve(address _approved, uint256 _tokenId) external payable`
  - The zero address indicates there is no approved address.
  - Throws unless `msg.sender`
    - is the current NFT owner
    - or an authorized operator of the current owner

- setApprovalForAll(): Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets.
    - Emits the ApprovalForAll event. ALSO: `function setApprovalForAll(address _operator, bool _approved) external`
    - The contract MUST allow multiple operators per owner.
    - Param `_operator` is the address to add to the set of authorized operators
    - Param `_approved`, set it to `true` to set operator as approved, `false` to revoke approval

- getApproved(): Returns the approved address for a single NFT or the zero address if there is none. ALSO: `function getApproved(uint256 _tokenId) external view returns (address)`
    - Throws if `_tokenId` is not a valid NFT.
    
- isApprovedForAll(): Returns true if an address is an authorized operator for another address. ALSO: `function isApprovedForAll(address _owner, address _operator) external view returns (bool)`
  • Events:
- Transfer: MUST trigger when ownership of any NFT changes by any mechanism. ALSO: `Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId)`
  - Case 1: NFTs are created (`from` == 0) 
  - Case 2: NFTs are destroyed (`to` == 0)
  - Exception: During contract creation, any number of NFTs may be created and assigned without emitting `Transfer` event. At the time of any transfer, the approved address for that NFT (if any) is reset to none.

- Approval: MUST emit when the approved address for an NFT is changed or reaffirmed. ALSO: `event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId)`
  - The zero address indicates there is no approved address.
  - ALSO: When a `Transfer` event emits, this also indicates that the approved address for that NFT (if any) is reset to none.
  
- ApprovalForAll: Must trigger when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner. ALSO: `event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)`

ERC165

interface ERC165 {...}
**Contents of contract as below:**
- supportsInterface(): Returns `true` if the contract implements `interfaceID` and `interfaceID` is not 0xffffffff, `false` otherwise. ALSO: `function supportsInterface(bytes4 interfaceID) external view returns (bool)`
    - Param `bytes32 interfaceID` The interface identifier, as specified in ERC-165
    - Interface identification is specified in ERC-165. This function uses less than 30,000 gas.

ERC721TokenReceiver (Wallet Interface):

  • A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
// ERC-165 identifier for this interface is 0x150b7a02
interface ERC721TokenReceiver{...} Contents of this contract as below:
- onERC721Received(): Handle the receipt of an NFT. ALSO: `function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4)`
    - The ERC721 smart contract calls this function on the recipient after a `transfer`.
    - This function MAY throw to revert and reject the transfer.
    - Return of other than the magic value MUST result in the transaction being reverted. Note: The contract address is always the message sender.
    - Returns:
        - `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    - Params:
        - `_operator` is the address which called `safeTransferFrom` function.
        - `_from` is the address which previously owned the token.
        - `_tokenId` is the NFT identifier which is being transferred
        - `_data` is the additional data with no specified format

NOTE: A contract that implements ERC721Metadata or ERC721Enumerable SHALL also implement ERC721. ERC-721 implements the requirements of interface ERC-165.

ERC721Metadata:

The metadata extension is OPTIONAL for ERC-721 smart contracts (see “caveats”). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.

//  Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface ERC721Metadata {...}
- name(): Returns a descriptive name for a collection of NFTs in this contract. ALSO: `function name() external view returns (string _name)`
- symbol(): Returns an abbreviated name for NFTs in this contract ALSO: `function symbol() external view returns (string _symbol)`
- tokenURI(): Returns a distinct Uniform Resource Identifier (URI) for a given asset. ALSO: `function tokenURI(uint256 _tokenId) external view returns (string)`
    - 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".

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."
        }
    }
}

ERC721Enumerable:

interface ERC721Enumerable {...}
Contents of this contract as below:

- totalSupply(): Returns 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. ALSO: `function totalSupply() external view returns (uint256)`
- tokenByIndex(): Returns the token identifier for the `_index`th NFT, (sort order not specified).
    - Param `_index` is a counter less than `totalSupply()`
    - Throws if `_index` >= `totalSupply()`
- tokenOfOwnerByIndex(): Returns the token identifier for the `_index`th NFT assigned to `_owner`, (sort order not specified) ALSO: `function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256)`
    - Param `_index` is a counter less than `balanceOf(_owner)`
    - Throws if
        - `_index` >= `balanceOf(_owner)`
        - or if `_owner` is the zero address, representing invalid NFTs.

NFT Identifiers

Every NFT is identified by a unique uint256 ID inside the ERC-721 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. 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 NFTs MAY become invalid (be destroyed). Please see the enumeration 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 safeTransferFrom function (overloaded with and without a bytes parameter)
  • an unsafe transferFrom function. 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
  • Blocklist certain address from receiving NFTs — prior art, CryptoKitties deployed contract, lines 565, 566
  • Disallow unsafe transfers — transferFrom 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 safeTransferFrom, 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.

Creation of NFTs (“minting”) and destruction of 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.

We questioned if the operator parameter on onERC721Received was necessary. In all cases we could imagine, if the operator was important then that operator could transfer the token to themself and then send it – then they would be the from address. This seems contrived because we consider the operator to be a temporary owner of the token (and transferring to themself is redundant). When the operator sends the token, it is the operator acting on their own accord, NOT the operator acting on behalf of the token holder. This is why the operator and the previous token owner are both significant to the token recipient.

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 requires a for-loop, return a Solidity 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

  • 0xcert ERC-721 Token includes test cases written using Truffle.

Implementations

  • 0xcert ERC721 – a reference implementation
    • MIT licensed, so you can freely use it for your projects
    • Includes test cases
    • Active bug bounty, you will be paid if you find errors
  • 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.

Issues:

Discussions:

NFT Implementations and Other Projects:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment