Appearance
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, 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:
approve
will lead to approval for one NFT related to an id.setApprovalForAll
will lead to approval of all NFTs owned bymsg.sender
.
Backwards Compatibility ​
This standard is compatible with EIP-721, EIP-1155, and EIP-2981.
Reference Implementation ​
Examples ​
Deploying an EIP-721 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.