Skip to content

Commit

Permalink
feat: create Names
Browse files Browse the repository at this point in the history
  • Loading branch information
NicoAcosta committed Nov 5, 2024
1 parent 28b94d8 commit 56eb32c
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 0 deletions.
94 changes: 94 additions & 0 deletions src/names/INames.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import { IERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";

/// @title INames - ERC721 Name Registration Interface
/// @notice Interface for a decentralized name registration system using NFTs
/// @dev Extends IERC721Enumerable to provide name registration and management functionality
interface INames is IERC721Enumerable {
/// @notice Emitted when a new name is minted
/// @param user Address of the user minting the name
/// @param tokenId ID of the minted NFT
/// @param name The registered name
event NameMinted(address indexed user, uint256 indexed tokenId, string indexed name);

/// @notice Emitted when the contract URI is updated
/// @param newURI New URI for contract metadata
event ContractURIUpdated(string newURI);

/// @notice Emitted when the token URI is updated
/// @param newURI New URI for token metadata
event TokenURIUpdated(string newURI);

/// @notice Error thrown when name exceeds maximum allowed length
error LongName();

/// @notice Error thrown when name is shorter than minimum required length
error ShortName();

/// @notice Error thrown when attempting to register an already taken name
/// @param name The name that was attempted to be registered
/// @param owner Current owner of the name
error NameAlreadyTaken(string name, address owner);

/// @notice Error thrown when a user attempts to register multiple names
/// @param user Address of the user
/// @param name Current name of the user
error UserAlreadyHasName(address user, string name);

/// @notice Mints a new name token
/// @dev Creates a new NFT representing the name ownership
/// @param name The name to register
/// @return mintedTokenId The ID of the newly minted NFT
/// @custom:throws LongName If name length exceeds maximum
/// @custom:throws ShortName If name length is below minimum
/// @custom:throws NameAlreadyTaken If name is already registered
/// @custom:throws UserAlreadyHasName If caller already owns a name
function mintName(string memory name) external returns (uint256 mintedTokenId);

/// @notice Retrieves the owner address for a given name
/// @param name The name to query
/// @return user The address that owns the name (zero address if unregistered)
function nameToUser(string memory name) external view returns (address user);

/// @notice Retrieves the registered name for a given user
/// @param user The address to query
/// @return name The user's registered name (empty string if none)
function userToName(address user) external view returns (string memory name);

/// @notice Retrieves the name associated with a token ID
/// @param tokenId The ID of the name token
/// @return name The name associated with the token
function tokenIdToName(uint256 tokenId) external view returns (string memory name);

/// @notice Checks if a name is available for registration
/// @param name The name to check
/// @return bool True if the name is available, false otherwise
function isAvailable(string memory name) external view returns (bool);

/// @notice Checks if an address has a registered name
/// @param user The address to check
/// @return bool True if the address has a name, false otherwise
function hasName(address user) external view returns (bool);

/// @notice Returns the contract-level metadata URI
/// @return URI string for contract metadata
function contractURI() external view returns (string memory);

/// @notice The minimum allowed length for names
/// @return uint256 Minimum name length (3)
function MINIMAL_NAME_LENGTH() external view returns (uint256);

/// @notice The maximum allowed length for names
/// @return uint256 Maximum name length (30)
function MAX_NAME_LENGTH() external view returns (uint256);

/// @notice Updates the contract-level metadata URI
/// @param _newURI New URI string for contract metadata
function updateContractURI(string memory _newURI) external;

/// @notice Updates the token-level metadata URI
/// @param _newURI New URI string for token metadata
function updateTokenURI(string memory _newURI) external;
}
125 changes: 125 additions & 0 deletions src/names/Names.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import { INames } from "./INames.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

/// @title Names - Decentralized Name Registration System
/// @author [Your Name/Organization]
/// @notice This contract implements a decentralized name registration system where users can mint unique names as NFTs
/// @dev Extends ERC721Enumerable for enumerable NFT functionality and implements custom INames interface
/// @custom:security-contact [email protected]
contract Names is ERC721Enumerable, INames, Ownable {
/// @notice URI for contract metadata
/// @dev Used for OpenSea and other marketplaces to display collection information
/// @inheritdoc INames
string public contractURI;

/// @notice Base URI for token metadata
/// @dev Used as the base for all token URIs
string private _tokenURI;

/// @notice Counter for generating unique token IDs
/// @dev Increments by 1 for each new mint
uint256 private _tokenIds;

/// @notice Minimum allowed length for a name
/// @dev Prevents extremely short names
/// @inheritdoc INames
uint256 public constant MINIMAL_NAME_LENGTH = 3;

/// @notice Maximum allowed length for a name
/// @dev Prevents excessive gas costs and maintains reasonable name lengths
/// @inheritdoc INames
uint256 public constant MAX_NAME_LENGTH = 30;

// State mappings
/// @notice Maps name strings to their owner addresses
/// @dev Primary lookup for name ownership
/// @inheritdoc INames
mapping(string name => address user) public nameToUser;
/// @inheritdoc INames
mapping(address user => string name) public userToName;
/// @inheritdoc INames
mapping(uint256 tokenId => string name) public tokenIdToName;

/// @notice Initializes the Names contract with basic metadata
/// @dev Sets initial URIs and configures base contract parameters
constructor() Ownable(msg.sender) ERC721("Plasa Names", "NAME") ERC721Enumerable() {
contractURI = "some-contract-uri";
_tokenURI = "some-token-uri";
}

/// @notice Updates the contract-level metadata URI
/// @dev Only callable by contract owner
/// @param _newURI New URI for contract metadata
/// @inheritdoc INames
function updateContractURI(string memory _newURI) public onlyOwner {
contractURI = _newURI;
emit ContractURIUpdated(_newURI);
}

/// @inheritdoc INames
function updateTokenURI(string memory _newURI) public onlyOwner {
_tokenURI = _newURI;
emit TokenURIUpdated(_newURI);
}

/// @inheritdoc IERC721Metadata
function tokenURI(uint256 /* tokenId */) public view override returns (string memory) {
return _tokenURI;
}

/// @notice Internal function to mint a new name token
/// @dev Handles validation and state updates for name minting
/// @param _user Address to mint the name for
/// @param _name Name to be minted
/// @return mintedTokenId The ID of the newly minted token
/// @custom:security Validates name availability and length constraints
function _mintName(address _user, string memory _name) internal returns (uint256 mintedTokenId) {
if (hasName(_user)) {
revert UserAlreadyHasName(_user, userToName[_user]);
}

if (!isAvailable(_name)) {
revert NameAlreadyTaken(_name, nameToUser[_name]);
}

uint256 nameLength = bytes(_name).length;

if (nameLength < MINIMAL_NAME_LENGTH) {
revert ShortName();
}

if (nameLength > MAX_NAME_LENGTH) {
revert LongName();
}

mintedTokenId = _tokenIds++;
_mint(_user, mintedTokenId);

nameToUser[_name] = _user;
userToName[_user] = _name;
tokenIdToName[mintedTokenId] = _name;

emit NameMinted(_user, mintedTokenId, _name);
}

/// @inheritdoc INames
function mintName(string memory name) public returns (uint256 mintedTokenId) {
return _mintName(msg.sender, name);
}

/// @inheritdoc INames
function isAvailable(string memory name) public view returns (bool) {
return nameToUser[name] == address(0);
}

/// @inheritdoc INames
function hasName(address user) public view returns (bool) {
return balanceOf(user) != 0;
}
}

0 comments on commit 56eb32c

Please sign in to comment.