Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5635: NFT 许可协议

用于检索 NFT 许可协议的预言机

Authors Timi (@0xTimi), 0xTriple7 (@ysqi)
Created 2022-08-10
Discussion Link https://ethereum-magicians.org/t/eip-5635-discussion-nft-licensing-agreement-standard/10779
Requires EIP-165, EIP-721, EIP-1155, EIP-2981

摘要

本 EIP 标准化了一个 NFT 许可预言机,用于存储(注册)和检索(发现)为非同质化代币(NFT)衍生作品授予的许可协议,这些衍生作品也是 NFT,但使用其它底层 NFT 的属性创建。

在本标准中,NFT 衍生作品被称为 dNFT,而原始底层 NFT 被称为 oNFT

NFT 所有者,被称为 licensor(许可方),可以授权另一个创建者,被称为 licensee(被许可方),以创建衍生作品(dNFT),以换取约定的付款,被称为 Royalty(版权费)。许可协议概述了许可方和被许可方之间交易相关的条款和条件。

规范

本文档中的关键词“必须”,“禁止”,“需要”,“应该”,“不应该”,“推荐”,“可以”和“可选”应按照 RFC 2119 中的描述进行解释。

一般来说,此标准中有三个重要的角色:

  • oNFT:原始底层 NFT。oNFT 的持有者是许可方。oNFT 可以是任何 NFT。
  • dNFT:基于一个或多个 oNFT 的衍生作品。dNFT 的持有者是被许可方。
  • Registry(注册机构):一个可信的智能合约,能够验证凭证是否由 oNFT 的持有者签名或发布。

每个 dNFT 合约必须实现 IERC5635NFTIERC165 接口。

pragma solidity ^0.6.0;
import "./IERC165.sol";

///
/// @notice NFT Licensing Standard 中 NFT 衍生品 (dNFT) 的接口
/// @dev 此接口的 ERC-165 标识符是 0xd584841c。
interface IERC5635DNFT is IERC165 {

    /// 要添加到接口数组的 ERC165 字节 - 设置在实现此标准的父合约中
    ///
    /// bytes4(keccak256("IERC5635DNFT{}")) == 0xd584841c
    /// bytes4 private constant _INTERFACE_ID_IERC5635DNFT = 0xd584841c;
    /// _registerInterface(_INTERFACE_ID_IERC5635XDNFT);
    
    /// @notice 获取凭证的数量。
    /// @param _tokenId - 被查询的 dNFT 资产的 ID
    /// @return _number - 凭证的数量
    function numberOfCredentials(
		uint256 _tokenId
    ) external view returns (
        uint256 _number
    );

    /// @notice 使用销售价格调用以确定欠多少版权费以及欠给谁。
    /// @param _tokenId - 被查询的 dNFT 资产的 ID
    /// @param _credentialId - 许可协议凭证的 ID,最大 ID 是 numberOfCredentials(_tokenId)-1
    /// @return _oNFT - 许可来自的 oNFT 地址
    /// @return _tokenID - 许可来自的 oNFT ID
    /// @return _registry - 可以验证此凭证的注册机构的地址
    function authorizedBy(
        uint256 _tokenId,
        uint256 _credentialId
    ) external view returns (
        address _oNFT,
        uint256 _tokenId,
        address _registry
    );
    
}

interface IERC165 {
    /// @notice 查询合约是否实现了接口
    /// @param interfaceID 接口标识符,如 ERC-165 中指定
    /// @dev 接口标识在 ERC-165 中指定。这个函数
    ///  使用不到 30,000 gas。
    /// @return 如果合约实现了 `interfaceID` 并且
    ///  `interfaceID` 不是 0xffffffff,则返回 `true`,否则返回 `false`
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

每个 Registry 合约必须实现 IERC5635RegistryIERC165 接口。

pragma solidity ^0.6.0;
import "./IERC165.sol";

///
/// @dev NFT Licensing Standard 中 NFT 衍生品 (dNFT) 的接口
///  注意:此接口的 ERC-165 标识符是 0xb5065e9f
interface IERC5635Registry is IERC165 {

    /// 要添加到接口数组的 ERC165 字节 - 设置在实现此标准的父合约中
    ///
    /// bytes4(keccak256("IERC5635Registry{}")) == 0xb5065e9f
    /// bytes4 private constant _INTERFACE_ID_IERC5635Registry = 0xb5065e9f;
    /// _registerInterface(_INTERFACE_ID_IERC5635Registry);

    // TODO: 语法是否正确?
    enum LicensingAgreementType {
      NonExclusive,
      Exclusive,
      Sole
    } 


    /// @notice 
    /// @param _dNFT - 
    /// @param _dNFT_Id - 
    /// @param _oNFT - 
    /// @param _oNFT_Id - 
    /// @return _licensed - 
    /// @return _tokenID - 许可来自的 oNFT ID
    /// @return _registry - 可以验证此凭证的注册机构的地址
    function isLicensed(
        address _dNFT,
        uint256 _dNFT_Id,
        address _oNFT,
        uint256 _oNFT_Id
    ) external view returns (
        bool _licensed
    );
    
    /// @return _licenseIdentifier - 标识符,例如 `MIT` 或 `Apache`,类似于 SPDX 中的 `SPDX-License-Identifier: MIT`。
    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 // 小数位是 9,表示将费率除以 1,000,000,000
    );
}

Registry 合约可以实现 IERC5635LicensingIERC165 接口。

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: 抵押贷款与否?
    
    function approve(address indexed _oNFT, address _approved, uint256 _tokenId) external payable; //TODO: 为什么是 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);

}

理由

可以使用 authorizedBy 从 dNFT 的合约中检索许可凭证,该凭证指定了许可协议的详细信息,其中可能包括 oNFT。这些凭证可以通过 registry 服务进行验证。

任何人都可以通过注册机构使用 licensingRoyalty 检索许可版税信息。虽然无法在链上强制执行此 EIP 中规定的规则,就像 EIP-2981 一样,但我们鼓励 NFT 市场遵守此 EIP。

两个阶段:许可和发现

以铸造 dNFT 的时刻作为分界点,之前的阶段称为许可阶段,之后的阶段称为发现阶段。接口 IERC5635Licensing 用于许可阶段,接口 IERC5635DNFTIERC5635Registry 用于发现阶段。

设计决策:许可协议的受益人

一旦有人出售他们的 NFT,完整的许可权利将没有任何负担地传递给新的所有者,因此受益人应该是新的所有者。

CantBeEvil 许可证和许可协议之间的区别。

CantBeEvil 许可证是创建者-持有者许可证,它表明 NFT 持有者从创建者那里获得的权利。与此同时,许可协议是许可方和被许可方之间的合同。因此,CantBeEvil 许可证不能用作许可协议。

设计决策:不同批准级别之间的关系

被批准的地址可以代表 oNFT 的持有者将许可协议 license()dNFT。我们定义了两个级别的批准:

  1. approve 将导致批准与 ID 相关的某个 NFT。
  2. setApprovalForAll 将导致批准 msg.sender 拥有的所有 NFT。

向后兼容性

此标准与 EIP-721EIP-1155EIP-2981 兼容。

参考实现

例子

部署一个 EIP-721 NFT 并发出支持 dNFT 的信号

constructor (string memory name, string memory symbol, string memory baseURI) {
        _name = name;
        _symbol = symbol;
        _setBaseURI(baseURI);
        // 注册受支持的接口以通过 ERC165 符合 ERC721
        _registerInterface(_INTERFACE_ID_ERC721);
        _registerInterface(_INTERFACE_ID_ERC721_METADATA);
        _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
        // dNFT 接口
        _registerInterface(_INTERFACE_ID_IERC5635DNFT);
}

检查在您的市场上出售的 NFT 是否是 dNFT

bytes4 private constant _INTERFACE_ID_IERC5635DNFT = 0xd584841c;

function checkDNFT(address _contract) internal returns (bool) {
    (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_IERC5635DNFT);
    return success;
}

检查地址是否是注册机构

bytes4 private constant _INTERFACE_ID_IERC5635Registry = 0xb5065e9f;

function checkLARegistry(address _contract) internal returns (bool) {
    (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_IERC5635Registry);
    return success;
}

安全考虑

需要讨论。

版权

CC0 下放弃版权和相关权利。

Citation

Please cite this document as:

Timi (@0xTimi), 0xTriple7 (@ysqi), "ERC-5635: NFT 许可协议 [DRAFT]," Ethereum Improvement Proposals, no. 5635, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5635.