Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-4393: 用于 NFT 和多重代币的小额支付

一个用于小费代币的接口,允许向 NFT 和多重代币的持有者支付小费

Authors Jules Lai (@julesl23)
Created 2021-10-24
Discussion Link https://ethereum-magicians.org/t/eip-proposal-micropayments-standard-for-nfts-and-multi-tokens/7366
Requires EIP-165, EIP-721, EIP-1155

摘要

本标准概述了一个智能合约接口,用于向非同质化代币和多重代币支付小费。代币持有者可以将小费作为 EIP-20 奖励提取。

为了本 EIP 的目的,小额支付指的是一种金融交易,通常涉及一小笔钱,称为“小费”,作为对特定 EIP-721 NFT 和 EIP-1155 多重代币持有者的奖励。持有者(也称为控制者)用作所有者的更通用术语,因为 NFT 可能代表非数字资产,例如服务。

动机

一种向任何类型的 NFT 或多重代币发送小费的廉价方式。这可以通过 gas 优化小费代币合约并使用接口中的 tipBatch 函数批量发送小费来实现。

为了便于实现到 dapps 中,提供一种小费服务来奖励 NFT 和多重代币持有者。允许将收入更公平地分配回用户社区的 NFT 持有者。

使接口尽可能精简,以便能够应用于许多不同的用例。

一些用例包括:

  • 游戏内购买和其他虚拟商品

  • 小费消息、帖子、音乐和视频内容

  • 捐赠/众筹

  • 版税分配

  • 每次点击付费广告

  • 激励服务使用

  • 奖励卡和优惠券

这些都可以利用区块链的安全性、即时性和透明度。

规范

本标准提案概述了一种通过实现 ITipToken 接口来允许支付小费的通用方法。为了允许最大程度的用例,该接口被有意地保持在最低限度。

实现此 EIP 标准的智能合约必须实现此 EIP 接口中的所有函数。还必须发出接口中指定的事件,以便可以仅从发出的事件中得出小费代币合约的完整状态。

实现此 EIP 标准的智能合约必须实现 EIP-165 supportsInterface 函数,并且如果通过 interfaceID 参数传递 0xE47A7022,则必须返回常量值 true。请注意,本文档中的 revert 可能意味着带有或不带有错误消息的 require、throw(不推荐,因为它已贬值)或 revert solidity 语句。

请注意,代码中以及本文档中提到的 nft(或大写的 NFT)也可能指 EIP-1155 同质化代币。

interface ITipToken {
    /**
        @dev 当小费代币实现批准 NFT 的地址以进行小费支付时,会发出此事件。
        'nft' 的持有者被批准接收奖励。
        当 NFT Transfer 事件发出时,这也表示该 NFT 的批准地址(如果有)已重置为 none。
        注意:此接口的 ERC-165 标识符为 0x985A3267。
    */
    event ApprovalForNFT(
        address[] holders,
        address indexed nft,
        uint256 indexed id,
        bool approved
    );

    /**
        @dev 当用户已将 ERC-20 兼容代币存入
        小费代币的合约地址或外部地址时,会发出此事件。
        这也表示存款已兑换为
        一定数量的小费代币
    */
    event Deposit(
        address indexed user,
        address indexed rewardToken,
        uint256 amount,
        uint256 tipTokenAmount
    );

    /**
        @dev 当持有者提取一定数量的 ERC-20 兼容
        奖励时,会发出此事件。此奖励来自小费代币的合约地址或来自
        外部地址,具体取决于小费代币的实现
    */
    event WithdrawReward(
        address indexed holder,
        address indexed rewardToken,
        uint256 amount
    );

    /**
        @dev 每次执行小费代币构造函数或初始化方法时,都会发出此事件。
        重要的是,要用作 NFT 持有者奖励的 ERC-20 兼容代币 'rewardToken_'
        此时设置,并在整个小费代币合约的生命周期内保持不变。
        'rewardToken_' 和 'tipToken_' MAY 相同。
    */
    event InitializeTipToken(
        address indexed tipToken_,
        address indexed rewardToken_,
        address owner_
    );

    /**
        @dev 每次用户向 NFT 持有者支付小费时,都会发出此事件。
        还包括奖励代币地址和奖励代币金额,该奖励代币金额
        将被保留,直到持有者提取奖励代币为止。
    */
    event Tip(
        address indexed user,
        address[] holder,
        address indexed nft,
        uint256 id,
        uint256 amount,
        address rewardToken,
        uint256[] rewardTokenAmount
    );

    /**
        @notice 启用或禁用对持有者持有的单个 NFT 或
        持有者共享的多重代币的小费支付批准
        @dev 如果调用 nft 的 supportsInterface 对于 IERC721 或 IERC1155 未返回
        true,则 MUST revert。
        如果任何 'holders' 是零地址,则 MUST revert。
        如果 'nft' 尚未批准小费代币合约地址作为运营商,则 MUST revert。
        MUST 发出“ApprovalForNFT”事件以反映批准或不批准。
        @param holders NFT 的持有者(NFT 控制者)
        @param nft NFT 合约地址
        @param id NFT 代币 ID
        @param approved 如果 'holder' 获得批准,则为 True,如果撤销批准,则为 false
    */
    function setApprovalForNFT(
        address[] calldata holders,
        address nft,
        uint256 id,
        bool approved
    ) external;

    /**
        @notice 检查是否已通过 setApprovalForNFT
        批准了带有代币 'id' 的 'holder' 和 'nft'
        @dev 这不检查 NFT 的持有者是否已更改。
        这留给实施者来检测所有权更改的事件并采取适当的措施
        @param holder NFT 的持有者(NFT 控制者)
        @param nft NFT 合约地址
        @param id NFT 代币 ID
        @return 如果小费代币合约先前已
        批准了带有代币 'id' 的 'holder' 和 'nft',则为 True
    */
    function isApprovalForNFT(
        address holder,
        address nft,
        uint256 id
    ) external returns (bool);

    /**
        @notice 将小费从 msg.sender 发送到单个 NFT 的持有者或
        发送到多重代币的共享持有者
        @dev 如果 'nft' 未被批准用于小费支付,则 MUST revert
        如果 'nft' 是零地址,则 MUST revert。
        MUST 将小费 'amount' 刻录到 'holder',并将奖励发送到
        持有者的待处理帐户。
        如果 'nft' 是具有多个持有者的多重代币,则每个持有者
        MUST 按其多重代币余额的比例接收小费金额
        MUST 发出“Tip”事件以反映 msg.sender 支付给 'nft' 的持有者的金额。
        @param nft NFT 合约地址
        @param id NFT 代币 ID
        @param amount 要发送给 NFT 持有者的小费代币金额
    */
    function tip(
        address nft,
        uint256 id,
        uint256 amount
    ) external;

    /**
        @notice 将一批小费发送给 'nfts' 的持有者以提高 gas 效率
        @dev 如果 NFT 未被批准用于小费支付,则 revert
        如果输入参数的长度不完全相同,则 MUST revert
        如果任何用户地址为零,则 MUST revert
        如果存在任何错误,则 MUST revert 整个批次
        MUST 发出“Tip”事件,以便可以从
        每个持有者收到的金额以及哪个 nft 以及从谁收到的信息重构。
        @param users 要支付小费的用户帐户
        @param nfts 要向其持有者支付小费的 NFT 合约地址
        @param ids 唯一标识 'nfts' 的 NFT 代币 ID
        @param amounts 要发送给 NFT 持有者的小费代币金额
    */
    function tipBatch(
        address[] calldata users,
        address[] calldata nfts,
        uint256[] calldata ids,
        uint256[] calldata amounts
    ) external;

    /**
        @notice 存入 ERC-20 兼容代币以换取小费代币
        @dev 每次存款的小费代币价格可能不同,因为
        发送的奖励代币数量最终是小费代币数量与
        用户的小费代币余额乘以用户的存款余额的比率。
        存款的代币可以保存在小费代币合约帐户中或
        在外部托管中。这将取决于小费代币的实现。
        每个小费代币合约 MUST 仅处理一种类型的 ERC-20 兼容
        存款奖励。
        此代币地址 SHOULD 传递给小费代币构造函数或
        初始化方法。如果用于存款的 ERC-20 奖励为
        零地址,则 SHOULD revert。
        MUST 发出“Deposit”事件,该事件显示用户、已存入的代币详细信息
        以及兑换的小费代币数量
        @param user 用户帐户
        @param amount 要存入以换取小费代币的 ERC-20 代币数量。
        此存款将在以后用作奖励代币
    */
    function deposit(address user, uint256 amount) external payable;

    /**
        @notice NFT 持有者可以在他们选择的时间提取他们的小费作为 ERC-20 兼容
        奖励
        @dev 如果没有足够的可用待处理余额可供提取,则 MUST revert。
        MUST 将 'amount' 发送到 msg.sender 帐户(持有者)
        MUST 减少待处理的奖励代币余额,减少 'amount' 的提取金额。
        MUST 发出“WithdrawReward”事件以显示提取的持有者、奖励
        代币地址和 'amount'
        @param amount 要提取作为奖励的 ERC-20 代币数量
    */
    function withdrawReward(uint256 amount) external payable;

    /**
        @notice MUST 与 ERC-20 balanceOf 具有相同的行为,是 'user' 持有的小费代币数量
        @param user 用户帐户
        @return 用户持有的小费代币余额
    */
    function balanceOf(address user) external view returns (uint256);

    /**
        @notice 当用户支付小费时,可用作奖励的存款余额
        @param user 用户帐户
        @return 存入的兼容 ERC-20 代币的剩余余额
    */
    function balanceDepositOf(address user) external view returns (uint256);

    /**
        @notice 欠 'holder' 的奖励代币数量
        @dev 待处理代币可能来自小费代币合约帐户
        或来自外部托管,具体取决于小费代币的实现
        @param holder NFT 的持有者(NFT 控制者)
        @return 欠持有者的小费代币数量
    */
    function rewardPendingOf(address holder) external view returns (uint256);
}

向持有者支付小费和奖励

用户首先将兼容的 EIP-20 存入小费代币合约,然后在托管中持有(减去任何约定的费用),以换取小费代币。然后,用户可以将这些小费代币发送到 NFT 和多重代币(这些代币已获得小费代币合约的批准以进行小费支付),以便由持有者在提款时兑换为原始 EIP-20 存款作为奖励。

小费代币转移和价值计算

小费代币价值与 EIP-20 存款进行交换,反之亦然。由小费代币实施者来决定小费代币的价格,以及因此在存入 EIP-20 时要铸造多少小费。一种可能性是为每个地理区域设置固定的转换率,以便来自较贫穷国家的那些用户能够发送与来自较富裕国家的那些用户相同数量的小费,以表示对内容/资产等的相同程度的赞赏。因此,在分析中发现哪些 NFT 实际上很受欢迎时,不会受到平均财富的Skew影响,从而使创作者能够拥有公平的竞争环境。

每当用户发送小费时,等值的已存入 EIP-20 MUST 被转移到 NFT 或多重代币持有者的待处理帐户,并且发送的小费代币 MUST 被烧毁。此等值使用一个简单的公式计算:

_EIP-20 存款的用户总余额 * 小费金额 / 小费代币的用户总余额*

因此,例如,向用户的 免费 小费余额添加小费只会降低该用户每笔小费的总体价值,因为它们集体仍然是指相同数量的 EIP-20 存款。

请注意,如果小费代币合约继承自 EIP-20,则小费可以直接从一个用户转移到另一个用户。存款金额将已在小费代币合约中(或外部托管帐户中),因此只需要更新小费代币合约的内部用户帐户与存款余额的映射即可。建议从用户 A 烧毁小费金额,然后以使用户 B 每笔小费的平均 EIP-20 存款价值保持不变的数量重新铸造回用户 B,以便在支付小费的过程中小费价值不会波动。

如果不继承自 EIP-20,则铸造小费代币 MUST 发出 event Transfer(address indexed from, address indexed to, uint256 value),其中 sender 是用于铸造的零地址,而 to 是用于烧毁的零地址。Transfer 事件 MUST 与 IERC20 接口中的 Transfer 函数具有相同的签名。

版税分配

EIP-1155 允许共享代币 ID 的持有者。想象一下,一篇由多个贡献者撰写的 NFT 代表的文章的情况。在这里,每个贡献者都是持有者,他们之间的分割份额百分比可以通过每个人在 EIP-1155 代币 ID 中持有的余额来表示。因此,对于 EIP-1155 代币 1 的两个持有者 A 和 B,如果持有者 A 的余额为 25,而持有者 B 的余额为 75,则发送给代币 1 的任何小费都将把待处理奖励的 25% 分配给持有者 A,并将剩余的 75% 分配给持有者 B。

以下是 ITipToken 合约数据结构的示例实现:

    /// 从 NFT/多重代币到代币 ID 到持有者的映射
    mapping(address => mapping(uint256 => address[])) private _tokenIdToHolders;

    /// 从用户到用户存款余额的映射
    mapping(address => uint256) private _depositBalances;

    /// 从持有者到持有者待处理奖励金额的映射
    mapping(address => uint256) private _rewardsPending;

这可以应对必须具有唯一代币 ID 和单个持有者(以符合标准)的 EIP-721 合约,以及每个实例可以具有多个代币 ID 和多个持有者的 EIP-1155 合约。然后,tip 函数实现将通过索引 NFT/多重代币地址和代币 ID 访问 _tokenIdToHolders,以分配给持有者的 _rewardsPending

对于需要将版税直接分配给持有者的情况,ITipToken 合约的 tip 方法的实现 MAY 将版税金额直接从用户的帐户发送到单个 NFT 的持有者或多重代币的共享持有者,减去可选的约定的费用。在这种情况下,小费代币类型是奖励代币。

注意事项

为了使 ITipToken 接口保持简单和通用,每个小费代币合约 MUST 一次使用一种 EIP-20 兼容存款类型。如果需要小费支付支持多个 EIP-20 存款,则每个小费代币合约 MUST 针对所需的每种 EIP-20 兼容类型单独部署。因此,如果需要从 ETH 和 BTC 包装的 EIP-20 存款中支付小费,则小费代币合约会部署两次。小费代币合约的构造函数 REQUIRED 传入小费代币合约支持的存款的 EIP-20 代币地址。或者,对于可升级的小费代币合约,初始化方法 REQUIRED 传入 EIP-20 代币地址。

此 EIP 未提供有关 EIP-20 奖励存款的存放位置的详细信息。它 MUST 在持有者从存款池中提取其应得的奖励时可用。建议的实现方式是将存款锁定在小费代币合约地址中。通过保留一个记录欠持有者的余额的映射结构,然后当用户支付小费时,存款可以保留在原地,并且只有当持有者将其作为奖励提取时,才会转移到持有者的地址。

此标准未指定允许的与 EIP-20 兼容的存款类型。实际上,可能是小费代币本身。但是,建议在转账后检查存款余额以了解实际的存款金额,以保持内部会计的一致性。例如,如果 EIP-20 合约收取费用,从而减少了实际存入的金额。

此标准未指定任何用于退还存款或已发送小费代币的功能,这留给实施者将其添加到他们的智能合约中。这样做的原因是保持接口的轻量级,而不是强制实施者需要退款,而是将其作为一种选择。

最小化 Gas 成本

通过在链下缓存小费,然后将它们批量调用 ITipToken 接口的 tipBatch 方法,基本上,初始化事务的成本只需支付一次,而不是每笔小费支付一次。此外,如果同一用户发送给同一 NFT 代币的多个小费累积在一起并作为批次中的一个条目发送,则可以在链下进一步节省 gas。

通过将发送给同一 NFT 的用户分组在一起可以进一步节省费用,以便每个组仅执行一次检查 NFT 的有效性以及它是否为 EIP-721 或 EIP-1155 的操作。

巧妙地最小化每个用户的存款余额和每个持有者的奖励余额的链上状态更新,如果预先对批次进行排序,则可以帮助进一步最小化批量发送时的 gas 成本。例如,可以避免检查批次中的下一个 NFT 是否相同。这留给小费代币合约的实施者。无论应用什么优化,它 MUST 仍然允许从发出的 Tip 和 TipBatch 事件中重构哪个帐户向哪个帐户支付了小费以及支付了什么 NFT 的信息。

基本原理

简单性

ITipToken 接口使用最少数量的函数,以便使其用途尽可能通用,同时有足够的函数来指导实现,以实现其向 NFT 持有者进行小额支付的目的。

使用 NFT

每个 NFT 都是一个独特的非同质化代币数字资产,存储在区块链上,并通过其地址和代币 ID 唯一标识。在安全区块链上使用加密散列进行真实烧录意味着它可以作为链接到唯一数字资产、服务或其他合同协议的锚点。此类用例可能包括(但实际上仅受想象力和接受度的限制):

  • 数字艺术、收藏品、音乐、视频、许可证和证书、活动门票、ENS 名称、游戏物品、元宇宙中的物品、物理物品的真实性证明、服务协议等。

此机制允许 NFT 的消费者安全地轻松地向 NFT 持有者支付小费和奖励。

新的商业模式

以音乐用例为例。传统上,自从该行业从 CD 等物理介质上分发的音频过渡到通过流媒体进行在线数字分发模型以来,音乐行业一直由有助于过渡的寡头垄断控制。他们运营一种固定的订阅模式,并从中设置向内容创作者(例如歌手、音乐家等)的版税分配金额。使用小费代币代表了音乐爱好者奖励内容创作者的另一种方式。每首歌曲或曲目都由 NFT 表示,粉丝可以向他们喜欢的歌曲(因此是 NFT)支付小费,反过来,NFT 的内容创作者可以获得购买小费的 EIP-20 奖励。粉丝主导的音乐行业与去中心化和代币化预计将带来新的收入,并使粉丝和内容创作者之间的距离更近。

在其他行业的各个领域,都可以应用类似的理念,即第三方控制者转向更促进的角色,而不是当今存在的货币控制角色。

保证审计跟踪

随着 Ethereum 生态系统的持续增长,许多 dapps 依赖于传统的数据库和浏览器 API 服务来检索和分类数据。此 EIP 标准保证智能合约发出的事件日志 MUST 提供足够的数据来创建所有当前小费代币和 EIP-20 奖励余额的准确记录。数据库或浏览器可以提供任何实施此标准的小费代币合约发出的事件中,发送给 NFT 持有者的每个小费代币和奖励的索引和分类搜索。因此,可以仅从发出的事件中重构小费代币合约的状态。

向后兼容性

如果允许将代币直接发送给其他用户,则小费代币合约可以与 EIP-20 规范完全兼容,并继承一些函数,例如 transfer。请注意,balanceOf 已被采用,并且 MUST 是用户地址持有的小费数量。例如,如果继承自 OpenZeppelin 对 EIP-20 代币的实现,则其合约负责维护小费代币的余额。因此,小费代币 balanceOf 函数 SHOULD 仅直接调用父(super)合约的 balanceOf 函数。

尚未转移到小费代币标准的是其他用户的消费者的能力。目前,此标准没有预见到对此的需求。

此 EIP 没有强调对小费代币二级市场或其他使用案例的需求,在这些案例中,使用名称而不是地址来识别小费代币类型可能很有用,因此这些函数已从 ITipToken 接口中删除,并且是实施者的职责范围。

安全考虑事项

虽然建议用户的存款锁定在小费代币合约或外部托管帐户中,并且不应用于 NFT 持有者的奖励之外的任何用途,但这是无法强制执行的。此标准规定,当持有者从存款池中提取奖励时,奖励 MUST 可用。

在任何用户可以向 NFT 支付小费之前,NFT 的持有者必须授予小费代币合约支付小费的批准。此标准规定 NFT 的持有者收到奖励。小费代币合约代码 SHOULD 清楚地表明这样做,而不会混淆奖励的去向。在接受存款之前,SHOULD 向用户明确任何费用。存在恶意实施者可能试图劫持潜在小费收入流以用于自身目的的风险。但是,小费过程的交易数量和频率应使这种类型的欺诈行为能够更快被发现。

版权

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

Citation

Please cite this document as:

Jules Lai (@julesl23), "ERC-4393: 用于 NFT 和多重代币的小额支付 [DRAFT]," Ethereum Improvement Proposals, no. 4393, October 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4393.