Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-5218: NFT Rights Management #5222

Merged
merged 32 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
81fa42e
New EIP draft: NFT Rights Management
iseriohn Jul 11, 2022
e3a3462
update eip identifier
iseriohn Jul 11, 2022
c9e9aa2
Update EIPS/eip-5222.md
iseriohn Jul 12, 2022
2dc209c
changing eip to 5218
iseriohn Jul 12, 2022
eaae0d2
Merge branch 'nft-rights-management' of github.com:iseriohn/EIP-NFT-R…
iseriohn Jul 12, 2022
359c0e9
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
25a4181
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
5186462
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
b5c4082
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
5ab6dd3
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
c115b11
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
c1f0d18
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
20f6547
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
59a9110
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
3dcf5df
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
c18b5b0
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
01d8fd6
Update EIPS/eip-5218.md
iseriohn Jul 12, 2022
9a5eff4
modify external urls and add images and smart contracts
iseriohn Jul 12, 2022
7f6bd5d
get rid of TODOs
iseriohn Jul 12, 2022
52cefe1
Update assets/eip-5218/contracts/test/Contract.t.sol
iseriohn Jul 14, 2022
fde70c3
Update assets/eip-5218/contracts/src/RightsManagement.sol
iseriohn Jul 14, 2022
942061f
Update assets/eip-5218/contracts/src/IERC5218.sol
iseriohn Jul 14, 2022
e9854df
add github handle
iseriohn Jul 14, 2022
ad79fbe
Update EIPS/eip-5218.md
iseriohn Jul 18, 2022
d960b35
change eip number from 5218 to 5222
iseriohn Jul 18, 2022
93c9113
fix EIPW Validator errors
iseriohn Jul 18, 2022
fe240c1
fix EIPW Validator errors
iseriohn Jul 18, 2022
f852fd9
add a paragraph on persistent license uri
iseriohn Jul 18, 2022
403ab32
add IC3 NFT License
iseriohn Jul 20, 2022
4ab2a07
Update EIPS/eip-5222.md
iseriohn Jul 20, 2022
6c7c875
Update EIPS/eip-5222.md
iseriohn Jul 20, 2022
1547507
changing eip number from 5222 to 5218
iseriohn Jul 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions EIPS/eip-5218.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
---
eip: 5218
iseriohn marked this conversation as resolved.
Show resolved Hide resolved
title: NFT Rights Management
description: An interface for creating copyright licenses that transfer with an NFT.
author: James Grimmelmann (@grimmelm), Yan Ji (@iseriohn), Tyler Kell (@relyt29)
discussions-to: https://ethereum-magicians.org/t/eip-5218-nft-rights-management/9911
status: Draft
type: Standards Track
category: ERC
created: 2022-07-11
requires: 721
iseriohn marked this conversation as resolved.
Show resolved Hide resolved
---



## 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 license can optionally be revoked under conditions specified by the creator. The holder of an active license can issue sublicenses to others to carry out the rights granted under the license.


## Motivation

The [EIP-721](./eip-721.md) 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 [EIP-721](./eip-721.md)) or rental (see [EIP-4907](./eip-4907.md)).

This EIP attempts to provide a standard to facilitate rights management of NFTs in the world of Web3. In particular, [EIP-5218](./eip-5218.md) 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 EIP-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 EIP-5218 compliant contract *must* implement the `IERC5218` interface**:

```solidity
pragma solidity ^0.8.0;

/// @title EIP-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:

<img src="../assets/eip-5218/license-tree.png" alt="The license tree" width=45% />

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 "EIP-5218 Metadata JSON Schema" as below, which adopts the "three-layer" design of the Creative Commons Licenses:

```json
{
"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 EIP-721 standard only logs the property but not the legal rights tethered to NFTs. Even when logging the license via the optional EIP-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 EIP-721 and allows efficient traceability.

## Backwards Compatibility

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

## Test Cases

Test cases are available [here](../assets/eip-5218/contracts/test/Contract.t.sol).

## Reference Implementation

A reference implementation maintains the following data structures:

```solidity
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 `EIP-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](../assets/eip-5218/contracts/src/RightsManagement.sol).

In addition, the [IC3 NFT License](../assets/eip-5218/ic3license/ic3license.pdf) 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

Copyright and related rights waived via [CC0](../LICENSE.md).

12 changes: 12 additions & 0 deletions assets/eip-5218/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EIP-5218 Reference Implementations

This is the source code for a reference implementation of EIP-5218.

## Build and Test

The repo expects a [Foundry](https://github.com/foundry-rs/foundry/tree/master/forge) build system, optionally using visual studio code for editing. You can run the test suite with:

```bash
forge test -vvvvv
```

6 changes: 6 additions & 0 deletions assets/eip-5218/contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[default]
src = 'src'
out = 'out'
libs = ['lib']

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
3 changes: 3 additions & 0 deletions assets/eip-5218/contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
forge-std/=lib/forge-std/src/
ds-test/=lib/forge-std/lib/ds-test/src/
@openzeppelin/=lib/openzeppelin-contracts/
Loading