ERC-7634: 有限转移次数 NFT
一种 ERC-721 扩展,用于限制 NFT 之间的可转移性
Authors | Qin Wang (@qinwang-git), Saber Yu (@OniReimu), Shiping Chen <shiping.chen@data61.csiro.au> |
---|---|
Created | 2024-02-22 |
Requires | EIP-165, EIP-721 |
摘要
本标准扩展了 ERC-721,引入了一种机制,允许铸造者通过一个名为 TransferCount
的参数来定制 NFT 的可转移性。TransferCount
设置了 NFT 可以被转移的次数限制。该标准规定了一个接口,其中包括用于设置和检索转移限制、跟踪转移计数以及定义转移前和转移后状态的函数。该标准能够更精细地控制 NFT 的所有权和转移权利,确保 NFT 可以被编程为具有特定的、可执行的转移限制。
动机
一旦 NFT 被售出,它们就会从铸造者(创建者)处分离,并且此后可以永久转移。然而,许多情况下需要对 NFT 的发行进行精确控制。我们从三个维度概述了它们的优势。
首先,通过限制 NFT 的销售或交易频率,可以保护稀有 NFT 的价值。例如,在拍卖中,限制一个令人垂涎的项目竞标轮数可以维持其溢价(尤其是在荷兰式拍卖中)。同样,在知识产权领域,专利可以被有限的转移次数所约束,之后才能被自由访问(进入 CC0)。在游戏领域,诸如武器、服装和车辆之类的物品可能具有有限的寿命,每次使用或交换都会导致磨损,最终在达到预定阈值时自动报废(销毁)。
其次,强制限制交易频率可以通过减轻与恶意 NFT 套利相关的风险(包括高频交易 (HFT))来增强网络安全和稳定性。虽然这提出了一个常见的漏洞,但缺乏易于部署和有效的方法来解决它,使得我们的方法特别有价值。
此外,限制转移的轮数可以减轻与(重新)质押 NFT 相关的经济风险,从而抑制潜在的泡沫。随着重新质押机制的快速发展,可以预见的是,用户可能很快就会参与多轮 NFT 质押(例如,NFT → stNFT → st^2NFT),类似于使用像 EigenLayer (Ethereum)、Babylon (Bitcoin) 和 Picasso (Solana) 这样的第三方平台质押流动性代币。值得注意的是,EigenLayer 当前的设置采用 NFT 作为参与者的重新质押位置(一种重新质押证明)。如果此 NFT 被重复重新质押到市场中,它可能会放大杠杆作用并加剧泡沫动态。通过限制质押迭代的次数,我们可以主动防止质押生态系统中出现类似庞氏骗局的动态。
主要收获
此标准提供了以下几个优点:
受控的价值保存:通过允许铸造者为 NFT 设置自定义的转移限制,此标准有助于保存数字资产的价值。正如实物收藏品通常会因稀缺性而获得或保持价值一样,限制 NFT 的转移次数可以帮助确保其价值随时间的推移而持续。
确保预期用途:设置转移限制可以确保 NFT 以符合其预期用途的方式使用。例如,如果 NFT 代表限量版数字艺术品,则限制转移可以防止其被过度交易并可能贬值。
扩展用例:这些增强功能通过为创建者和所有者提供更多的控制和灵活性,从而拓宽了 NFT 的潜在应用。例如,NFT 可以用于表示具有有限可转移性的会员资格或许可证,从而为数字所有权模型开辟新的可能性。
易于集成:为了确保广泛采用和易于集成,此标准扩展了现有的 ERC-721 接口。通过定义一个单独的接口(IERC7634
),其中包括新函数,该标准允许现有的 ERC-721 合同以最小的更改来采用新功能。这种方法促进了向后兼容性,并鼓励将转移限制无缝地整合到当前的 NFT 项目中。
规范
本文档中的关键词“必须”,“不得”,“必需”,“应该”,“不应该”,“推荐”,“可以”和“可选”应解释为 RFC 2119 中所述。
setTransferLimit
:一个为 tokenId 建立转移限制的函数。transferLimitOf
:一个检索 tokenId 的转移限制的函数。transferCountOf
:一个返回 tokenId 的当前转移计数的函数。
该标准的实现者必须具有以下所有函数:
pragma solidity ^0.8.4;
/// @title IERC7634 有限可转移 NFT 接口
/// @dev ERC7634 有限可转移 NFT 扩展的接口,适用于 ERC721
/// @author Saber Yu
interface IERC7634 {
/**
* @dev 当转移计数被设置或更新时发出
*/
event TransferCount(uint256 indexed tokenId, address owner, uint256 counts);
/**
* @dev 返回 tokenId 的当前转移计数
*/
function transferCountOf(uint256 tokenId) external view returns (uint256);
/**
* @dev 设置 tokenId 的转移限制。只能由 token 所有者或批准的地址调用。
* @param tokenId 要设置限制的 token 的 ID
* @param limit 允许 token 转移的最大次数
*/
function setTransferLimit(uint256 tokenId, uint256 limit) external;
/**
* @dev 返回 tokenId 的转移限制
*/
function transferLimitOf(uint256 tokenId) external view returns (uint256);
}
理由
跟踪内部转移计数重要吗?
是,也不是。它是可选的,并且完全取决于实际需求。如果您选择跟踪,下面给出的参考实现是一个推荐的实现。_incrementTransferCount
函数和相关的检索函数(transferLimitOf
和 transferCountOf
)旨在跟踪 NFT 已经经历的转移次数。这种内部跟踪机制对于强制执行铸造者的转移限制至关重要,确保一旦达到限制,就不会发生进一步的转移。
如果选择跟踪,这就是我们可能想要跟踪的全部内容吗?
建议还跟踪之前和之后的状态。可选的 _beforeTokenTransfer
和 _afterTokenTransfer
函数被重写以定义 NFT 在转移之前和之后的状态。这些函数确保根据转移限制和计数执行任何必要的检查或更新。通过将这些检查集成到转移过程中,该标准确保始终执行转移限制。
向后兼容性
通过添加扩展函数集,此标准可以完全与 ERC-721 兼容。
扩展
除了现有的 NFT 协议之外,还可以使用其他高级功能来增强此标准。例如:
-
结合销毁函数(例如,ERC-5679)将使 NFT 在达到其转移限制后自动过期,类似于 Snapchat 消息在多次查看后消失的短暂特性。
-
结合 SBT 标准中定义的非转移函数,将使 NFT 在预定数量的交易后与单个所有者结算并绑定绑定。此功能类似于投标人最终在参与多轮投标后获得资金的情况。
参考实现
推荐的实现如下所示:
_incrementTransferCount
:一个内部函数,用于增加转移计数。_beforeTokenTransfer
:一个重写的函数,用于定义转移之前的状态。_afterTokenTransfe
:一个重写的函数,用于概述转移之后的状态。
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC7634.sol";
/// @title ERC721 的有限可转移 NFT 扩展
/// @dev 有限可转移 NFT 扩展的实现,适用于 ERC721
/// @author Saber Yu
contract ERC7634 is ERC721, IERC7634 {
// 从 tokenId 到转移计数的映射
mapping(uint256 => uint256) private _transferCounts;
// 从 tokenId 到其最大转移限制的映射
mapping(uint256 => uint256) private _transferLimits;
/**
* @dev 参见 {IERC7634-transferCountOf}。
*/
function transferCountOf(uint256 tokenId) public view override returns (uint256) {
require(_exists(tokenId), "ERC7634: Nonexistent token");
return _transferCounts[tokenId];
}
/**
* @dev 参见 {IERC7634-setTransferLimit}。
*/
function setTransferLimit(uint256 tokenId, uint256 limit) public override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7634: caller is not owner nor approved");
_transferLimits[tokenId] = limit;
}
/**
* @dev 参见 {IERC7634-transferLimitOf}。
*/
function transferLimitOf(uint256 tokenId) public view override returns (uint256) {
require(_exists(tokenId), "ERC7634: Nonexistent token");
return _transferLimits[tokenId];
}
/**
* @dev 用于增加转移计数的内部函数。
*/
function _incrementTransferCount(uint256 tokenId) internal {
_transferCounts[tokenId] += 1;
emit TransferCount(tokenId, ownerOf(tokenId), _transferCounts[tokenId]);
}
/**
* @dev 重写 {_beforeTokenTransfer} 以强制执行转移限制。
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override {
require(_transferCounts[tokenId] < _transferLimits[tokenId], "ERC7634: Transfer limit reached");
super._beforeTokenTransfer(from, to, tokenId);
}
/**
* @dev 重写 {_afterTokenTransfer} 以处理转移后逻辑。
*/
function _afterTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 quantity
) internal virtual override {
_incrementTransferCount(tokenId);
if (_transferCounts[tokenId] == _transferLimits[tokenId]) {
// 达到限制后可选的转移后操作
// 根据所需的行为取消注释以下内容,例如 `burn` 操作
// ---------------------------------------
// _burn(tokenId); // 销毁 token
// ---------------------------------------
}
super._afterTokenTransfer(from, to, tokenId, quantity);
}
/**
* @dev 重写 {supportsInterface} 以声明对 IERC7634 的支持。
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
return interfaceId == type(IERC7634).interfaceId || super.supportsInterface(interfaceId);
}
}
安全考虑
- 确保每个 NFT 铸造者都可以调用此函数来设置转移限制。
- 考虑使转移限制在设置后不可变,以防止篡改或未经授权的修改。
- 避免在与高级功能集成时执行资源密集型操作,这可能会超过执行期间的 gas 限制。
版权
版权和相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Qin Wang (@qinwang-git), Saber Yu (@OniReimu), Shiping Chen <shiping.chen@data61.csiro.au>, "ERC-7634: 有限转移次数 NFT [DRAFT]," Ethereum Improvement Proposals, no. 7634, February 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7634.