ERC-4400: ERC-721 Consumer Extension

Abstract

This specification defines standard functions outlining a consumer role for instance(s)
of ERC-721. An implementation allows reading the current consumer for a
given NFT (tokenId) along with a standardized event for when an consumer has changed. The proposal depends on and
extends the existing ERC-721.

Motivation

Many ERC-721 contracts introduce their own custom role that grants permissions
for utilising/consuming a given NFT instance. The need for that role stems from the fact that other than owning the NFT
instance, there are other actions that can be performed on an NFT. For example, various metaverses useoperator
/contributor
roles for Land (ERC-721), so that owners of the land can authorise other addresses to deploy scenes to them (f.e.
commissioning a service company to develop a scene).

It is common for NFTs to have utility other than simply owning it. That being said, it requires a separate standardized
consumer role, allowing compatibility with user interfaces and contracts, managing those contracts.

Having a consumer role will enable protocols to integrate and build on top of dApps that issue ERC721 tokens.

Example of kinds of contracts and applications that can benefit from this standard are predominantly metaverses that
have land and other types of digital assets in those metaverses (scene deployment on land, renting
land/characters/clothes/passes to events etc.)

Specification

The keywords “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 contract compliant to the ERC721 Consumer extension MUST implement the ERC721Consumer interface. The consumer extension is OPTIONAL for ERC-721 contracts.

/// @title ERC-721 Consumer Role extension
/// Note: the ERC-165 identifier for this interface is 0x953c8dfa
interface ERC721Consumer /* is ERC721 */ {
    /// @notice This emits when consumer of a _tokenId changes.
    /// address(0) used as previousConsumer indicates that there was no consumer set prior to this event
    /// address(0) used as a newConsumer indicates that the consumer role is absent  
    event ConsumerChanged(address indexed previousConsumer, address indexed newConsumer);
    /// @notice Get the consumer of a token
    /// @dev address(0) consumer address indicates that there is no consumer currently set for that token
    /// @param _tokenId The identifier for a token
    /// @return The address of the consumer of the token
    function consumerOf(uint256 _tokenId) view external returns (address);
    /// @notice Set the address of the new consumer for the given token instance
    /// @dev Throws unless `msg.sender` is the current owner, an authorised operator, or the approved address for this token. Throws if `_tokenId` is not valid token
    /// @dev Set _newConsumer to address(0) to renounce the consumer role
    /// @param _newConsumer The address of the new consumer for the token instance
    function changeConsumer(address _newConsumer, uint256 _tokenId) external;
}

Every contract implementing the ERC721Consumer extension is free to define the permissions of a consumer (e.g. what
are consumers allowed to do within their system) with only one exception - consumers MUST NOT be considered owners,
authorised operators or approved addresses as per the ERC721 specification. Thus, they MUST NOT be able to execute
transfers & approvals.

The consumerOf() function MAY be implemented as pure or view.

The changeConsumer(address _newConsumer, uint256 _tokenId) function MAY be implemented as public or external.

The ConsumerChanged event MUST be emitted when a consumer is changed.

Rationale

Key factors influencing the standard:

  • Keeping the number of functions in the interfaces to a minimum to prevent contract bloat.
  • Simplicity
  • Gas Efficiency
  • Not reusing or overloading other already existing roles (e.g. owners, operators, approved addresses)

Name

The chosen name resonates with the purpose of its existence. Consumers can be considered entities that utilise the token
instances, without necessarily having ownership rights to it.

The other name for the role that was considered was operator, however it is already defined and used within
the ERC721 standard.

Restriction on the Permissions

There are numerous use-cases where a distinct role for NFTs is required that MUST NOT have owner permissions. A contract
that implements the consumer role and grants ownership permissions to the consumer renders this standard pointless.

Backwards Compatibility

There are no other standards that define a similar role for NFTs and the name (consumer) is not used by other ERC721
related standards.

Reference Implementation

The following is a snippet for reference implementation of the ERC721Consumer extension. The full repository can be
found here

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC721Consumer.sol";
contract ConsumerImpl is IERC721Consumer, ERC721 {
    mapping(uint256 => address) consumers;
    constructor() ERC721("ReferenceImpl", "RIMPL") {
    }
    function consumerOf(uint256 _tokenId) view external returns (address) {
        return consumers[_tokenId];
    }
    function changeConsumer(address _newConsumer, uint256 _tokenId) external {
        require(msg.sender == this.ownerOf(_tokenId), "IERC721Consumer: caller is not owner nor approved");
        address previousConsumer = consumers[_tokenId];
        consumers[_tokenId] = _newConsumer;
        emit ConsumerChanged(previousConsumer, _newConsumer);
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
        return interfaceId == type(IERC721Consumer).interfaceId || super.supportsInterface(interfaceId);
    }
}
1 Like

Link to the EIP PR - Optional ERC721Consumer Extension by Daniel-K-Ivanov · Pull Request #4400 · ethereum/EIPs · GitHub

Hi Daniel, I’m super happy you’re promoting this EIP.
I think we should promote both this EIP and this one in parallel.

One comment I have is that with the current proposal, only the owner of the NFT can call the changeConsumer function. I think it would be good to add (or use) the mechanism of the ERC721 to aprove third parties to be able to use this function.

I imagine that there will be 3rd parties platforms (dapps) which NFT owners will use in order take advantage of this feature. Therefore the contract should give the 3rd parties the permission to change the consumers without the NFT owner having to do any action.

What do you think?

Thank you for the feedback!

Are you referring to the approved and operator addresses that can be authorised by the owner of the ERC-721 to spend/manage the tokens? If yes, I think that we can do that and will update the reference implementation with your suggestion

Yes. That would be great.
From the openzeplin implementation:

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

I do wonder if the approved addresses should be the same as the ERC721 approved addresses. Or should it be a different dictionary instead → let’s say _tokenConsumeApprovals.

I think I’d go with the 1st option, but it is worth a thought.

1 Like

Just found this EIP.
It is much more elaborate and as you can see it is stagnant and has no activity for a long time.
edit: probably because it is gas expansive.

I keep studying solidity, and I think that there’s no way to use your proposed standard for current ERC721 projects without migrating the contracts, so this is a major drawback.

The EIP has been updated with comments received from the community and implementors of the proposal.

@MindfulFroggie I’ve addressed your feedback by enabling approved address + operators to be able to change the consumer. Let me know what you think :slight_smile:

1 Like

Hey there! I was looking over your EIP (I left a review earlier which you resolved), and was wondering if you might want to switch the current setup so that there can be an array of addresses that are consumers instead of just one, since the current setup only allows for one address per tokenId. This doesn’t contradict cases where only one consumer is desired, since the implementing contract can always set a cap on how many addresses can be pushed to the array.

1 Like

At some point, I was thinking the same thing. Indeed there might be use-cases where you would like to have more than one consumer. I will think about it and maybe even update the proposal with your suggestion. I would like to see how much overhead it would introduce implementation/gas cost wise.

Thank you for the feedback though!

1 Like