Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5218: NFT Rights Management

An interface for creating copyright licenses that transfer with an NFT.

Authors James Grimmelmann (@grimmelm), Yan Ji (@iseriohn), Tyler Kell (@relyt29)
Created 2022-07-11
Discussion Link https://ethereum-magicians.org/t/eip-5218-nft-rights-management/9911
Requires EIP-721

Abstract

The following standard defines an API for managing NFT licenses. This standard provides basic functionality to create, transfer, and revoke licenses, and to determine the current licensing state of an NFT. The standard does not define the legal details of the license. Instead, it provides a structured framework for recording licensing details.

We consider use cases of NFT creators who wish to give the NFT holder a copyright license to use a work associated with the NFT. The holder of an active license can issue sublicenses to others to carry out the rights granted under the license. The license can be transferred with the NFT, so do all the sublicenses. The license can optionally be revoked under conditions specified by the creator.

Motivation

The ERC-721 standard defines an API to track and transfer ownership of an NFT. When an NFT is to represent some off-chain asset, however, we would need some legally effective mechanism to tether the on-chain asset (NFT) to the off-chain property. One important case of off-chain property is creative work such as an image or music file. Recently, most NFT projects involving creative works have used licenses to clarify what legal rights are granted to the NFT owner. But these licenses are almost always off-chain and the NFTs themselves do not indicate what licenses apply to them, leading to uncertainty about rights to use the work associated with the NFT. It is not a trivial task to avoid all the copyright vulnerabilities in NFTs, nor have existing EIPs addressed rights management of NFTs beyond the simple cases of direct ownership (see ERC-721) or rental (see ERC-4907).

This EIP attempts to provide a standard to facilitate rights management of NFTs in the world of Web3. In particular, ERC-5218 smart contracts allow all licenses to an NFT, including the root license issued to the NFT owner and sublicenses granted by a license holder, to be recorded and easily tracked with on-chain data. These licenses can consist of human-readable legal code, machine-readable summaries such as those written in CC REL, or both. An ERC-5218 smart contract points to a license by recording a URI, providing a reliable reference for users to learn what legal rights they are granted and for NFT creators and auditors to detect unauthorized infringing uses.

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 ERC-5218 compliant contract must implement the IERC5218 interface:

pragma solidity ^0.8.0;

/// @title ERC-5218: NFT Rights Management
interface IERC5218 is IERC721 {

  /// @dev This emits when a new license is created by any mechanism.
  event CreateLicense(uint256 _licenseId, uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string _uri, address _revoker);
 
  /// @dev This emits when a license is revoked. Note that under some
  ///  license terms, the sublicenses may be `implicitly` revoked following the
  ///  revocation of some ancestral license. In that case, your smart contract
  ///  may only emit this event once for the ancestral license, and the revocation
  ///  of all its sublicenses can be implied without consuming additional gas.
  event RevokeLicense(uint256 _licenseId);
 
  /// @dev This emits when the a license is transferred to a new holder. The
  ///  root license of an NFT should be transferred with the NFT in an ERC721
  ///  `transfer` function call. 
  event TransferLicense(uint256 _licenseId, address _licenseHolder);
  
  /// @notice Check if a license is active.
  /// @dev A non-existing or revoked license is inactive and this function must
  ///  return `false` upon it. Under some license terms, a license may become
  ///  inactive because some ancestral license has been revoked. In that case,
  ///  this function should return `false`.
  /// @param _licenseId The identifier for the queried license
  /// @return Whether the queried license is active
  function isLicenseActive(uint256 _licenseId) external view returns (bool);

  /// @notice Retrieve the token identifier a license was issued upon.
  /// @dev Throws unless the license is active.
  /// @param _licenseId The identifier for the queried license
  /// @return The token identifier the queried license was issued upon
  function getLicenseTokenId(uint256 _licenseId) external view returns (uint256);

  /// @notice Retrieve the parent license identifier of a license.
  /// @dev Throws unless the license is active. If a license doesn't have a
  ///  parent license, return a special identifier not referring to any license
  ///  (such as 0).
  /// @param _licenseId The identifier for the queried license
  /// @return The parent license identifier of the queried license
  function getParentLicenseId(uint256 _licenseId) external view returns (uint256);

  /// @notice Retrieve the holder of a license.
  /// @dev Throws unless the license is active.   
  /// @param _licenseId The identifier for the queried license
  /// @return The holder address of the queried license
  function getLicenseHolder(uint256 _licenseId) external view returns (address);

  /// @notice Retrieve the URI of a license.
  /// @dev Throws unless the license is active.   
  /// @param _licenseId The identifier for the queried license
  /// @return The URI of the queried license
  function getLicenseURI(uint256 _licenseId) external view returns (string memory);

  /// @notice Retrieve the revoker address of a license.
  /// @dev Throws unless the license is active.   
  /// @param _licenseId The identifier for the queried license
  /// @return The revoker address of the queried license
  function getLicenseRevoker(uint256 _licenseId) external view returns (address);

  /// @notice Retrieve the root license identifier of an NFT.
  /// @dev Throws unless the queried NFT exists. If the NFT doesn't have a root
  ///  license tethered to it, return a special identifier not referring to any
  ///  license (such as 0).   
  /// @param _tokenId The identifier for the queried NFT
  /// @return The root license identifier of the queried NFT
  function getLicenseIdByTokenId(uint256 _tokenId) external view returns (uint256);
  
  /// @notice Create a new license.
  /// @dev Throws unless the NFT `_tokenId` exists. Throws unless the parent
  ///  license `_parentLicenseId` is active, or `_parentLicenseId` is a special
  ///  identifier not referring to any license (such as 0) and the NFT
  ///  `_tokenId` doesn't have a root license tethered to it. Throws unless the
  ///  message sender is eligible to create the license, i.e., either the
  ///  license to be created is a root license and `msg.sender` is the NFT owner,
  ///  or the license to be created is a sublicense and `msg.sender` is the holder
  ///  of the parent license. 
  /// @param _tokenId The identifier for the NFT the license is issued upon
  /// @param _parentLicenseId The identifier for the parent license
  /// @param _licenseHolder The address of the license holder
  /// @param _uri The URI of the license terms
  /// @param _revoker The revoker address
  /// @return The identifier of the created license
  function createLicense(uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string memory _uri, address _revoker) external returns (uint256);

  /// @notice Revoke a license.
  /// @dev Throws unless the license is active and the message sender is the
  ///  eligible revoker. This function should be used for revoking both root
  ///  licenses and sublicenses. Note that if a root license is revoked, the
  ///  NFT should be transferred back to its creator.
  /// @param _licenseId The identifier for the queried license
  function revokeLicense(uint256 _licenseId) external;
  
  /// @notice Transfer a sublicense.
  /// @dev Throws unless the sublicense is active and `msg.sender` is the license
  ///  holder. Note that the root license of an NFT should be tethered to and
  ///  transferred with the NFT. Whenever an NFT is transferred by calling the
  ///  ERC721 `transfer` function, the holder of the root license should be
  ///  changed to the new NFT owner.
  /// @param _licenseId The identifier for the queried license
  /// @param _licenseHolder The new license holder
  function transferSublicense(uint256 _licenseId, address _licenseHolder) external;
}

Licenses to an NFT in general have a tree structure as below:

The license tree

There is one root license to the NFT itself, granting the NFT owner some rights to the linked work. The NFT owner (i.e., the root license holder) may create sublicenses, holders of which may also create sublicenses recursively.

The full log of license creation, transfer, and revocation must be traceable via event logs. Therefore, all license creations and transfers must emit a corresponding log event. Revocation may differ a bit. An implementation of this EIP may emit a Revoke event only when a license is revoked in a function call, or for every revoked license, both are sufficient to trace the status of all licenses. The former costs less gas if revoking a license automatically revokes all sublicenses under it, while the latter is efficient in terms of interrogation of a license status. Implementers should make the tradeoffs depending on their license terms.

The revoker of a license may be the licensor, the license holder, or a smart contract address which calls the revokeLicense function when some conditions are met. Implementers should be careful with the authorization, and may make the revoker smart contract forward compatible with transfers by not hardcoding the addresses of licensor or licenseHolder.

The license URI may point to a JSON file that conforms to the “ERC-5218 Metadata JSON Schema” as below, which adopts the “three-layer” design of the Creative Commons Licenses:

{
    "title": "License Metadata",
    "type": "object",
    "properties": {
        "legal-code": {
            "type": "string",
            "description": "The legal code of the license."
        },
        "human-readable": {
            "type": "string",
            "description": "The human readable license deed."
        },
        "machine-readable": {
            "type": "string",
            "description": "The machine readable code of the license that can be recognized by software, such as CC REL."
        }
    }
}

Note that this EIP doesn’t include a function to update license URI so the license terms should be persistent by default. It is recommended to store the license metadata on a decentralized storage service such as IPFS or adopt the IPFS-style URI which encodes the hash of the metadata for integrity verification. On the other hand, license updatability, if necessary in certain scenarios, can be realized by revoking the original license and creating a new license, or adding a updating function, the eligibile caller of which must be carefully specified in the license and securely implemented in the smart contract.

The supportsInterface method MUST return true when called with 0xac7b5ca9.

Rationale

This EIP aims to allow tracing all licenses to an NFT to facilitate right management. The ERC-721 standard only logs the property but not the legal rights tethered to NFTs. Even when logging the license via the optional ERC-721 Metadata extension, sublicenses are not traceable, which doesn’t comply with the transparency goals of Web3. Some implementations attempt to get around this limitation by minting NFTs to represent a particular license, such as the BAYC #6068 Royalty-Free Usage License. This is not an ideal solution because the linking between different licenses to an NFT is ambiguous. An auditor has to investigate all NFTs in the blockchain and inspect the metadata which hasn’t been standardized in terms of sublicense relationship. To avoid these problems, this EIP logs all licenses to an NFT in a tree data structure, which is compatible with ERC-721 and allows efficient traceability.

This EIP attempts to tether NFTs with copyright licenses to the creative work by default and is not subject to the high legal threshold for copyright ownership transfers which require an explicit signature from the copyright owner. To transfer and track copyright ownership, one may possibly integrate ERC-5218 and ERC-5289 after careful scrutinizing and implement a smart contract that atomically (1) signs the legal contract via ERC-5289, and (2) transfers the NFT together with the copyright ownership via ERC-5218. Either both take place or both revert.

Backwards Compatibility

This standard is compatible with the current ERC-721 standards: a contract can inherit from both ERC-721 and ERC-5218 at the same time.

Test Cases

Test cases are available here.

Reference Implementation

A reference implementation maintains the following data structures:

  struct License {
    bool active; // whether the license is active
    uint256 tokenId; // the identifier of the NFT the license is tethered to
    uint256 parentLicenseId; // the identifier of the parent license
    address licenseHolder; // the license holder
    string uri; // the license URI
    address revoker; // the license revoker
  }
  mapping(uint256 => License) private _licenses; // maps from a license identifier to a license object
  mapping(uint256 => uint256) private _licenseIds; // maps from an NFT to its root license identifier

Each NFT has a license tree and starting from each license, one can trace back to the root license via parentLicenseId along the path.

In the reference implementation, once a license is revoked, all sublicenses under it are revoked. This is realized in a lazy manner for lower gas cost, i.e., assign active=false only for licenses that are explicitly revoked in a revokeLicense function call. Therefore, isLicenseActive returns true only if all its ancestral licenses haven’t been revoked.

For non-root licenses, the creation, transfer and revocation are straightforward:

  1. Only the holder of an active license can create sublicenses.
  2. Only the holder of an active license can transfer it to a different license holder.
  3. Only the revoker of an active license can revoke it.

The root license must be compatible with ERC-721:

  1. When an NFT is minted, a license is granted to the NFT owner.
  2. When an NFT is transferred, the license holder is changed to the new owner of the NFT.
  3. When a root license is revoked, the NFT is returned to the NFT creator, and the NFT creator may later transfer it to a new owner with a new license.

The complete implementation can be found here.

In addition, the Token-Bound NFT License is specifically designed to work with this interface and provides a reference to the language of NFT licenses.

Security Considerations

Implementors of the IERC5218 standard must consider thoroughly the permissions they give to licenseHolder and revoker. If the license is ever to be transferred to a different license holder, the revoker smart contract should not hardcode the licenseHolder address to avoid undesirable scenarios.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

James Grimmelmann (@grimmelm), Yan Ji (@iseriohn), Tyler Kell (@relyt29), "ERC-5218: NFT Rights Management [DRAFT]," Ethereum Improvement Proposals, no. 5218, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5218.