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.
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.
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:
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
.
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.
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 are available here.
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:
The root license must be compatible with ERC-721
:
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.
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.