Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ERC: ERC-721 Name Registry Extension #292

Merged
merged 29 commits into from
Apr 16, 2024
Merged
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions ERCS/erc-7644.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
eip: 7644
title: ERC-721 Name Registry Extension
description: Add a time-limited unique naming registry mechanism to ERC-721 tokens.
chenly marked this conversation as resolved.
Show resolved Hide resolved
author: Chen Liaoyuan (@chenly)
discussions-to: https://ethereum-magicians.org/t/erc-7644-erc-721-name-registry-extension/19022
status: Draft
type: Standards Track
category: ERC
created: 2024-03-01
requires: 721
---

## Abstract

This extension defines an interface that adds a naming mechanism to [ERC-721](./eip-721.md) tokens. It allows each token to have a unique name with a set expiration date. The interface includes functions for assigning, updating, and querying names and their associated tokens, ensuring that names remain unique until they expire.
chenly marked this conversation as resolved.
Show resolved Hide resolved

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Implementers of this extension **MUST** have all of the following functions:

```solidity
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.
*/
interface INameRegistry {
chenly marked this conversation as resolved.
Show resolved Hide resolved
/**
* @dev Emitted when a token is named.
* @param tokenId The token ID that is being named.
* @param newName The new name assigned to the token.
* @param expiryDate The expiry date of the name registration.
*/
event TokenNamed(uint256 indexed tokenId, bytes32 newName, uint256 expiryDate);

/**
* @dev Emitted when a token with a name is minted.
* @param to The address of the token recipient.
* @param tokenId The token ID that is minted.
* @param name The name assigned to the newly minted token.
* @param expiryDate The expiry date of the name registration.
*/
event TokenMintedWithName(address indexed to, uint256 indexed tokenId, bytes32 name, uint256 expiryDate);

/**
* @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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
function getName(bytes32 _name) external view returns (uint256 tokenId, uint256 expiryDate);

}
```

## Rationale

Here are a few design decisions and why they were made:

chenly marked this conversation as resolved.
Show resolved Hide resolved
#### Intuitive Token IDs

Current token IDs are predominantly numeric, lacking intuitiveness. The extension of the [ERC-721](./eip-721.md) standard to support Named Tokens aims to facilitate the acceptance and use of this standard within the NFT marketplace. By moving beyond mere numbers to include distinctive names, token IDs can more directly reflect the unique identity or value they represent.

#### Expanding NFT Use Cases

By allowing each token to possess a unique name, we unlock new application scenarios in ecosystems built around scarce username resources, such as domain name registration systems. In such scenarios, domain owners can demonstrate ownership directly by holding the corresponding token, thereby broadening the application spectrum and enhancing the value of NFTs.

## Backwards Compatibility

This standard is fully [ERC-721](./eip-721.md) compatible.

## Reference Implementation

```solidity
pragma solidity ^0.8.0;

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

contract NameRegistry is ERC721 {
event TokenNamed(uint256 indexed tokenId, bytes32 newName, uint256 expiryDate);
event TokenMintedWithName(address indexed to, uint256 indexed tokenId, bytes32 name, uint256 expiryDate);
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("NameRegistry", "NRG") {}

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

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

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");

// Check if name is either not registered or expired
require(_nameRegistrations[_name].expiryDate <= block.timestamp, "NameRegistry: Name already in use and not expired");

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

if (oldName != bytes32(0)) {
emit NameChanged(tokenId, oldName, _name, expiryDate);
} else {
emit TokenNamed(tokenId, _name, expiryDate);
}

_lastSetNameTime[tokenId] = block.timestamp;
}

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

function _mint(address to, uint256 tokenId, bytes32 _name, uint256 duration) internal virtual {
super._mint(to, tokenId);
uint256 expiryDate = block.timestamp + duration;
_setTokenName(tokenId, _name, expiryDate);
emit TokenMintedWithName(to, tokenId, _name, expiryDate);
}

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

## Security Considerations

#### Mitigating Abusive Behaviors and Resource Hoarding

The design includes mechanisms to prevent abusive behaviors and resource hoarding. Minimum intervals for name setting and maximum durations for name expiry are established to deter spam and malicious attacks, limit rapid consecutive name registrations, and encourage fair and efficient use of naming resources. These measures mitigate potential security risks, ensuring names cannot be monopolized indefinitely and promoting a sustainable and equitable environment for all users.

#### Username Restrictions

To facilitate indexing and gas efficiency, usernames should adhere to a length constraint of 3 to 32 characters. This range prevents the registration of overly long names, which can be costly in terms of gas and difficult to manage. Limiting characters to the range of [a-zA-Z0-9] enhances readability and prevents the abuse of the naming system by restricting the use of special characters that could complicate domain resolution or user recognition. Implementing these constraints not only promotes a high level of usability within the ecosystem but also guards against the proliferation of spam registrations, ensuring that the registry remains accessible and functional for genuine users.
SamWilsn marked this conversation as resolved.
Show resolved Hide resolved

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
Loading