Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7644: ERC-721 名称注册扩展

将有时限的唯一名称扩展到 ERC-721 中的每个 token,利用名称注册表进行注册和管理。

Authors Chen Liaoyuan (@chenly)
Created 2024-03-01
Discussion Link https://ethereum-magicians.org/t/erc-7644-erc-721-name-registry-extension/19022
Requires EIP-721

摘要

此扩展定义了一个接口,用于向 ERC-721 token 添加命名机制。它允许每个 token 拥有一个具有设定过期日期的唯一名称,从而确保在当前 NFT 合约中的唯一性。该接口包括用于分配、更新和查询名称及其相关 token 的函数,从而确保名称在过期前保持唯一。负责设置名称的实体取决于使用此扩展时的具体用例场景。

动机

随着去中心化域名注册方法随着 NFT 的集成而发展,我们看到了将这种模式扩展到用户名领域的机会。通过将 token ID 与用户名相关联,我们增强了去中心化生态系统中实体直观识别。

这种集成有多种用途:

  • 直观性: 数字 token ID 缺乏直观的识别。通过包含用户名,token ID 变得更具代表性,从而提高了可用性。

  • 用户名经济探索: 注册机制为探索用户名经济开辟了途径,提供了诸如身份验证和社交互动等好处。

  • 与 NFT 的协同作用: 用户名与 NFT 的融合释放了协同增长,从而可以实现诸如经过身份验证的社交互动和个性化数字资产之类的新颖应用。

规范

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

此扩展的实现者必须具有以下所有功能:

pragma solidity ^0.8.0;

/**
 * @title INameRegistry
 * @dev Interface for the NameRegistry smart contract.
 * This interface allows interaction with a NameRegistry, 
 * enabling the registration, management, and lookup of names 
 * with associated expiry dates tied to specific tokens.
 * 此接口允许与 NameRegistry 智能合约进行交互,
 * 从而可以注册、管理和查找与特定 token 相关的具有关联过期日期的名称。
 */
interface IERC7644 /* is IERC721 */ {

    /**
     * @dev Emitted when the name of a token is changed.
     * @param tokenId The token ID whose name is changed.
     * @param oldName The previous name of the token.
     * @param newName The new name assigned to the token.
     * @param expiryDate The expiry date of the new name registration.
     * 当 token 的名称更改时发出。
     * @param tokenId 名称已更改的 token ID。
     * @param oldName 该 token 之前的名称。
     * @param newName 分配给该 token 的新名称。
     * @param expiryDate 新名称注册的到期日期。
     */
    event NameChanged(uint256 indexed tokenId, bytes32 oldName, bytes32 newName, uint256 expiryDate);

    /**
     * @dev Returns the name of the specified token, if the name has not expired.
     * @param tokenId The token ID to query for its name.
     * @return The name of the token, or an empty bytes32 if no name is set or it has expired.
     * 返回指定 token 的名称(如果该名称尚未过期)。
     * @param tokenId 要查询其名称的 token ID。
     * @return 该 token 的名称;如果未设置名称或已过期,则返回一个空的 bytes32。
     */
    function nameOf(uint256 tokenId) external view returns (bytes32);

    /**
     * @dev Returns the token ID associated with a given name, if the name registration has not expired.
     * @param _name The name to query for its associated token ID.
     * @return The token ID associated with the name, or zero if no token is found or the name has expired.
     * 返回与给定名称关联的 token ID(如果该名称注册尚未过期)。
     * @param _name 要查询其关联 token ID 的名称。
     * @return 与该名称关联的 token ID;如果未找到 token 或名称已过期,则返回零。
     */
    function tokenIdOf(bytes32 _name) external view returns (uint256);

    /**
     * @dev Allows a token owner to set or update the name of their token, subject to a duration for the name's validity.
     * @param tokenId The token ID whose name is to be set or updated.
     * @param _name The new name to assign to the token.
     * @param duration The duration in seconds for which the name is valid, starting from the time of calling this function.
     * Note: The name must be unique and not currently in use by an active (non-expired) registration.
     * 允许 token 所有者设置或更新其 token 的名称,但须受名称有效期的限制。
     * @param tokenId 要设置或更新其名称的 token ID。
     * @param _name 要分配给该 token 的新名称。
     * @param duration 名称有效的持续时间(以秒为单位),从调用此函数的时间开始计算。
     * 注意:该名称必须是唯一的,并且当前不能被处于有效(未过期)注册状态的用户使用。
     */
    function setName(uint256 tokenId, bytes32 _name, uint256 duration) external;

    /**
     * @dev Returns the tokenId and expiryDate for a given name, if the name registration has not expired.
     * @param _name The name to query for its associated token ID and expiry date.
     * @return tokenId The token ID associated with the name.
     * @return expiryDate The expiry date of the name registration.
     * 返回给定名称的 tokenId 和 expiryDate(如果该名称注册尚未过期)。
     * @param _name 要查询其关联 token ID 和到期日期的名称。
     * @return tokenId 与该名称关联的 token ID。
     * @return expiryDate 名称注册的到期日期。
     */
    function nameInfo(bytes32 _name) external view returns (uint256 tokenId, uint256 expiryDate);
	
}

理由

名称过期

通过为用户名实施有效期,我们引入了几个优势。这种机制确保了动态环境,其中可以释放未使用或过时的用户名,从而培养一个健康的生态系统。它鼓励用户名轮换,防止长期囤积并促进积极参与。用户有动力管理其用户名组合,续订有价值的名称,同时放弃不相关的名称。最终,这有助于公平和效率,确保命名资源得到有效利用和更新,以满足不断变化的需求。

名称唯一性

强制执行唯一的用户名对于维护清晰直观的身份识别系统至关重要。它可以防止混淆,并实现去中心化生态系统内的无缝交互。唯一的用户名增强了可发现性,并有助于在交易和社交互动中建立信任。此要求强调了去中心化环境中清晰度的重要性,在这种环境中,精确的识别对于建立信任和促进高效交互至关重要。

名称注册系统

引入用户名注册系统可防止滥用行为,并促进公平访问命名资源。保留和续订机制可防止垄断理想的用户名,同时使合法用户可以保护感兴趣的名称。保留确保公平的机会来声明所需的用户名,防止囤积和投机活动。续订机制鼓励积极参与和对命名生态系统的投资。总之,这些功能创造了一个平衡和包容的环境,从而培养了一个充满活力的用户社区。

向后兼容性

此标准与 ERC-721 完全兼容。

参考实现

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract ERC7644 is ERC721 {
    event NameChanged(uint256 indexed tokenId, bytes32 oldName, bytes32 newName, uint256 expiryDate);

    struct NameRegistration {
        uint256 tokenId;
        uint256 expiryDate;
    }

    mapping(uint256 => bytes32) private _tokenNames;
    mapping(bytes32 => NameRegistration) private _nameRegistrations;
    mapping(uint256 => uint256) private _lastSetNameTime;

    uint256 public constant MAX_DURATION = 10 * 365 days;
    uint256 public constant MIN_SET_NAME_INTERVAL = 1 days;

    constructor() ERC721("Asd Token", "ASDT") {}

    function nameOf(uint256 tokenId) public view returns (bytes32) {
        if(_tokenNames[tokenId] != bytes32(0) && _nameRegistrations[_tokenNames[tokenId]].expiryDate > block.timestamp)
        {
            return _tokenNames[tokenId];
        }else{
            return bytes32(0);
        }
    }

    function tokenIdOf(bytes32 _name) public view returns (uint256) {
        require(_nameRegistrations[_name].expiryDate > block.timestamp, "NameRegistry: Name expired");
        if(_nameRegistrations[_name].tokenId > 0)
        {
            return _nameRegistrations[_name].tokenId;
        }else{
            return uint256(0);
        }
    }

    function setName(uint256 tokenId, bytes32 _name, uint256 duration) public {
        require(ownerOf(tokenId) == msg.sender, "NameRegistry: Caller is not the token owner");
        require(duration <= MAX_DURATION, "NameRegistry: Duration exceeds maximum limit");
        require(block.timestamp - _lastSetNameTime[tokenId] >= MIN_SET_NAME_INTERVAL, "NameRegistry: Minimum interval not met");
        require(tokenIdOf(_name) == uint256(0) || tokenIdOf(_name) == tokenId, "NameRegistry: Name already in use and not expired");

        bytes32 oldName = _tokenNames[tokenId];
        uint256 expiryDate = block.timestamp + duration;
        _setTokenName(tokenId, _name, expiryDate);

        emit NameChanged(tokenId, oldName, _name, expiryDate);

        _lastSetNameTime[tokenId] = block.timestamp;
    }

    function nameInfo(bytes32 _name) public view returns (uint256, uint256) {
        require(_nameRegistrations[_name].tokenId > 0 && _nameRegistrations[_name].expiryDate > block.timestamp, "NameRegistry: Name expired or does not exist");
        NameRegistration memory registration = _nameRegistrations[_name];
        return (registration.tokenId, registration.expiryDate);
    }

    function _setTokenName(uint256 tokenId, bytes32 _name, uint256 expiryDate) internal {
        _tokenNames[tokenId] = _name;
        _nameRegistrations[_name] = NameRegistration(tokenId, expiryDate);
    }
}

安全考虑

减轻滥用行为和资源囤积

该设计包括防止滥用行为和资源囤积的机制。建立名称设置的最小间隔和名称过期的最大持续时间,以阻止垃圾邮件和恶意攻击,限制快速连续的名称注册,并鼓励公平有效地使用命名资源。这些措施减轻了潜在的安全风险,确保名称不能被无限期地垄断,并为所有用户创造一个可持续和公平的环境。

用户名限制

为了方便索引和提高 Gas 效率,用户名应遵守 3 到 32 个字符的长度限制。此范围可防止注册过长的名称,这在 Gas 方面成本高昂且难以管理。将字符限制在 [a-zA-Z0-9] 的范围内可以提高可读性,并通过限制使用可能使域名解析或用户识别复杂化的特殊字符来防止滥用命名系统。实施这些约束不仅可以提高生态系统中的可用性,还可以防止垃圾邮件注册的扩散,确保注册表对真正的用户保持可访问和功能性。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Chen Liaoyuan (@chenly), "ERC-7644: ERC-721 名称注册扩展 [DRAFT]," Ethereum Improvement Proposals, no. 7644, March 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7644.