forked from DecentralizedClimateFoundation/DCIPs
266 lines
10 KiB
Markdown
266 lines
10 KiB
Markdown
|
---
|
||
|
eip: 5635
|
||
|
title: NFT Licensing Agreements
|
||
|
description: An oracle for retrieving NFT licensing agreements
|
||
|
author: Timi (@0xTimi), 0xTriple7 (@ysqi)
|
||
|
discussions-to: https://ethereum-magicians.org/t/eip-5635-discussion-nft-licensing-agreement-standard/10779
|
||
|
status: Draft
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2022-08-10
|
||
|
requires: 165, 721, 1155, 2981
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
This EIP standardizes an NFT licensing oracle to store (register) and retrieve (discover) granted licensing agreements for non-fungible token (NFT) derivative works, which are also NFTs but are created using properties of some other underlying NFTs.
|
||
|
|
||
|
In this standard, an NFT derivative work is referred to as a **dNFT**, while the original underlying NFT is referred to as an **oNFT**.
|
||
|
|
||
|
The NFT owner, known as the `licensor`, may authorize another creator, known as the `licensee`, to create a derivative works (dNFTs), in exchange for an agreed payment, known as a `Royalty`. A licensing agreement outlines terms and conditions related to the deal between the licensor and licensee.
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
The key words "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.
|
||
|
|
||
|
In general, there are three important roles in this standard:
|
||
|
|
||
|
- oNFT: An original underlying NFT. The holder of an oNFT is a licensor. An oNFT can be any NFT.
|
||
|
- dNFT: A derivative work based on one or more oNFTs. The holder of a dNFT is a licensee.
|
||
|
- Registry: A trusted smart contract able to verify whether a credential is signed or released by the holder of oNFT.
|
||
|
|
||
|
Every **dNFT** contract must implement the `IERC5635NFT` and `IERC165` inferfaces.
|
||
|
|
||
|
```solidity
|
||
|
pragma solidity ^0.6.0;
|
||
|
import "./IERC165.sol";
|
||
|
|
||
|
///
|
||
|
/// @notice Interface of NFT derivatives (dNFT) for the NFT Licensing Standard
|
||
|
/// @dev The ERC-165 identifier for this interface is 0xd584841c.
|
||
|
interface IERC5635DNFT is IERC165 {
|
||
|
|
||
|
/// ERC165 bytes to add to interface array - set in parent contract
|
||
|
/// implementing this standard
|
||
|
///
|
||
|
/// bytes4(keccak256("IERC5635DNFT{}")) == 0xd584841c
|
||
|
/// bytes4 private constant _INTERFACE_ID_IERC5635DNFT = 0xd584841c;
|
||
|
/// _registerInterface(_INTERFACE_ID_IERC5635XDNFT);
|
||
|
|
||
|
/// @notice Get the number of credentials.
|
||
|
/// @param _tokenId - ID of the dNFT asset queried
|
||
|
/// @return _number - the number of credentials
|
||
|
function numberOfCredentials(
|
||
|
uint256 _tokenId
|
||
|
) external view returns (
|
||
|
uint256 _number
|
||
|
);
|
||
|
|
||
|
/// @notice Called with the sale price to determine how much royalty is owed and to whom.
|
||
|
/// @param _tokenId - ID of the dNFT asset queried
|
||
|
/// @param _credentialId - ID of the licensing agreement credential, the max id is numberOfCredentials(_tokenId)-1
|
||
|
/// @return _oNFT - the oNFT address where the licensing from
|
||
|
/// @return _tokenID - the oNFT ID where the licensing from
|
||
|
/// @return _registry - the address of registry which can verify this credential
|
||
|
function authorizedBy(
|
||
|
uint256 _tokenId,
|
||
|
uint256 _credentialId
|
||
|
) external view returns (
|
||
|
address _oNFT,
|
||
|
uint256 _tokenId,
|
||
|
address _registry
|
||
|
);
|
||
|
|
||
|
}
|
||
|
|
||
|
interface IERC165 {
|
||
|
/// @notice Query if a contract implements an interface
|
||
|
/// @param interfaceID The interface identifier, as specified in ERC-165
|
||
|
/// @dev Interface identification is specified in ERC-165. This function
|
||
|
/// uses less than 30,000 gas.
|
||
|
/// @return `true` if the contract implements `interfaceID` and
|
||
|
/// `interfaceID` is not 0xffffffff, `false` otherwise
|
||
|
function supportsInterface(bytes4 interfaceID) external view returns (bool);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Every **Registry** contract must implement the `IERC5635Registry` and `IERC165` inferfaces.
|
||
|
|
||
|
```solidity
|
||
|
pragma solidity ^0.6.0;
|
||
|
import "./IERC165.sol";
|
||
|
|
||
|
///
|
||
|
/// @dev Interface of NFT derivatives (dNFT) for the NFT Licensing Standard
|
||
|
/// Note: the ERC-165 identifier for this interface is 0xb5065e9f
|
||
|
interface IERC5635Registry is IERC165 {
|
||
|
|
||
|
/// ERC165 bytes to add to interface array - set in parent contract
|
||
|
/// implementing this standard
|
||
|
///
|
||
|
/// bytes4(keccak256("IERC5635Registry{}")) == 0xb5065e9f
|
||
|
/// bytes4 private constant _INTERFACE_ID_IERC5635Registry = 0xb5065e9f;
|
||
|
/// _registerInterface(_INTERFACE_ID_IERC5635Registry);
|
||
|
|
||
|
// TODO: Is the syntax correct?
|
||
|
enum LicensingAgreementType {
|
||
|
NonExclusive,
|
||
|
Exclusive,
|
||
|
Sole
|
||
|
}
|
||
|
|
||
|
|
||
|
/// @notice
|
||
|
/// @param _dNFT -
|
||
|
/// @param _dNFT_Id -
|
||
|
/// @param _oNFT -
|
||
|
/// @param _oNFT_Id -
|
||
|
/// @return _licensed -
|
||
|
/// @return _tokenID - the oNFT ID where the licensing from
|
||
|
/// @return _registry - the address of registry which can verify this credential
|
||
|
function isLicensed(
|
||
|
address _dNFT,
|
||
|
uint256 _dNFT_Id,
|
||
|
address _oNFT,
|
||
|
uint256 _oNFT_Id
|
||
|
) external view returns (
|
||
|
bool _licensed
|
||
|
);
|
||
|
|
||
|
/// @return _licenseIdentifier - the identifier, e.g. `MIT` or `Apache`, similar to `SPDX-License-Identifier: MIT` in SPDX.
|
||
|
function licensingInfo(
|
||
|
address _dNFT,
|
||
|
uint256 _dNFT_Id,
|
||
|
address _oNFT,
|
||
|
uint256 _oNFT_Id
|
||
|
) external view returns (
|
||
|
bool _licensed,
|
||
|
address _licensor,
|
||
|
uint64 _timeOfSignature,
|
||
|
uint64 _expiryTime,
|
||
|
LicensingAgreementType _type,
|
||
|
string _licenseName,
|
||
|
string _licenseUri //
|
||
|
);
|
||
|
|
||
|
function royaltyRate(
|
||
|
address _dNFT,
|
||
|
uint256 _dNFT_Id,
|
||
|
address _oNFT,
|
||
|
uint256 _oNFT_Id
|
||
|
) external view returns (
|
||
|
address beneficiary,
|
||
|
uint256 rate // The decimals is 9, means to divide the rate by 1,000,000,000
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The **Registry** contract MAY implement the `IERC5635Licensing` and `IERC165` inferfaces.
|
||
|
|
||
|
```solidity
|
||
|
pragma solidity ^0.6.0;
|
||
|
import "./IERC165.sol";
|
||
|
|
||
|
///
|
||
|
///
|
||
|
interface IERC5635Licensing is IERC165, IERC5635Registry {
|
||
|
|
||
|
event Licence(address indexed _oNFT, uint256 indexed _oNFT_Id, address indexed _dNFT, uint256 indexed _dNFT_Id, uint64 _expiryTime, LicensingAgreementType _type, string _licenseName, string _licenseUri);
|
||
|
|
||
|
event Approval(address indexed _oNFT, address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
|
||
|
|
||
|
event ApprovalForAll(address indexed _oNFT, address indexed _owner, address indexed _operator, bool _approved);
|
||
|
|
||
|
function licence(address indexed _oNFT, uint256 indexed _oNFT_Id, address indexed _dNFT, uint256 indexed _dNFT_Id, uint64 _expiryTime, LicensingAgreementType _type, string _licenseName, string _licenseUri) external payable; //TODO: mortgages or not?
|
||
|
|
||
|
function approve(address indexed _oNFT, address _approved, uint256 _tokenId) external payable; //TODO: why payable?
|
||
|
|
||
|
function setApprovalForAll(address indexed _oNFT, address _operator, bool _approved) external;
|
||
|
|
||
|
function getApproved(address indexed _oNFT, uint256 _tokenId) external view returns (address);
|
||
|
|
||
|
function isApprovedForAll(address indexed _oNFT, address _owner, address _operator) external view returns (bool);
|
||
|
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Rationale
|
||
|
|
||
|
Licensing credentials from a dNFT's contract can be retrieved with `authorizedBy`, which specifies the details of a licensing agreement, which may include the oNFT. Those credentials may be verified with a `registry` service.
|
||
|
|
||
|
Anyone can retrieve licensing royalty information with `licensingRoyalty` via the registry. While it is not possible to enforce the rules set out in this EIP on-chain, just like [EIP-2981](./eip-2981.md), we encourages NFT marketplaces to follow this EIP.
|
||
|
|
||
|
### Two stages: Licensing and Discovery
|
||
|
|
||
|
Taking the moment when the dNFT is minted as the cut-off point, the stage before is called the **Licensing** stage, and the subsequent stage is called the **Discovery** stage. The interface `IERC5635Licensing` is for the **Licensing** stage, and the interfaces `IERC5635DNFT` and `IERC5635Registry` are for the **Discovery** stage.
|
||
|
|
||
|
### Design decision: beneficiary of licensing agreement
|
||
|
|
||
|
As soon as someone sells their NFT, the full licensed rights are passed along to the new owner without any encumbrances, so that the beneficiary should be the new owner.
|
||
|
|
||
|
### Difference between CantBeEvil Licenses and Licensing Agreements.
|
||
|
|
||
|
CantBeEvil licenses are creator-holder licenses which indicate what rights the NFTs' holder are granted from the creator. Meanwhile, licensing agreements is a contract between a licensor and licensee. So, CantBeEvil licenses cannot be used as a licensing agreement.
|
||
|
|
||
|
### Design decision: Relationship between different approval levels
|
||
|
|
||
|
The approved address can `license()` the licensing agreement to **dNFT** on behalf of the holder of an **oNFT**. We define two levels of approval like that:
|
||
|
|
||
|
1. `approve` will lead to approval for one NFT related to an id.
|
||
|
2. `setApprovalForAll` will lead to approval of all NFTs owned by `msg.sender`.
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
This standard is compatible with [EIP-721](./eip-721.md), [EIP-1155](./eip-1155.md), and [EIP-2981](./eip-2981.md).
|
||
|
|
||
|
## Reference Implementation
|
||
|
|
||
|
### Examples
|
||
|
|
||
|
#### Deploying an [EIP-721](./eip-721.md) NFT and signaling support for dNFT
|
||
|
|
||
|
```solidity
|
||
|
constructor (string memory name, string memory symbol, string memory baseURI) {
|
||
|
_name = name;
|
||
|
_symbol = symbol;
|
||
|
_setBaseURI(baseURI);
|
||
|
// register the supported interfaces to conform to ERC721 via ERC165
|
||
|
_registerInterface(_INTERFACE_ID_ERC721);
|
||
|
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
|
||
|
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
|
||
|
// dNFT interface
|
||
|
_registerInterface(_INTERFACE_ID_IERC5635DNFT);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Checking if the NFT being sold on your marketplace is a dNFT
|
||
|
|
||
|
```solidity
|
||
|
bytes4 private constant _INTERFACE_ID_IERC5635DNFT = 0xd584841c;
|
||
|
|
||
|
function checkDNFT(address _contract) internal returns (bool) {
|
||
|
(bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_IERC5635DNFT);
|
||
|
return success;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Checking if an address is a Registry
|
||
|
|
||
|
```solidity
|
||
|
bytes4 private constant _INTERFACE_ID_IERC5635Registry = 0xb5065e9f;
|
||
|
|
||
|
function checkLARegistry(address _contract) internal returns (bool) {
|
||
|
(bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_IERC5635Registry);
|
||
|
return success;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Security Considerations
|
||
|
|
||
|
Needs discussion.
|
||
|
|
||
|
|
||
|
## Copyright
|
||
|
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|