-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
28b94d8
commit 56eb32c
Showing
2 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |