mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
130 lines
4.5 KiB
Solidity
130 lines
4.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.18;
|
|
|
|
import {ERC721} from '@openzeppelin/contracts/token/ERC721/ERC721.sol';
|
|
import {ImmutableOwnable} from 'contracts/misc/ImmutableOwnable.sol';
|
|
import {ILensHandles} from 'contracts/interfaces/ILensHandles.sol';
|
|
import {HandlesEvents} from 'contracts/namespaces/constants/Events.sol';
|
|
import {HandlesErrors} from 'contracts/namespaces/constants/Errors.sol';
|
|
import {HandleTokenURILib} from 'contracts/libraries/token-uris/HandleTokenURILib.sol';
|
|
import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
|
|
|
|
/**
|
|
* A handle is defined as a local name inside a namespace context. A handle is represented as the local name with its
|
|
* namespace applied as a suffix, using the dot symbol as separator.
|
|
*
|
|
* handle = ${localName}.${namespace}
|
|
*
|
|
* Handle and local name can be used interchangeably once you are in a context of a namespace, as it became redundant.
|
|
*
|
|
* handle === ${localName} ; inside some namespace.
|
|
*/
|
|
contract LensHandles is ERC721, ImmutableOwnable, ILensHandles {
|
|
uint256 internal constant MAX_HANDLE_LENGTH = 31;
|
|
string internal constant NAMESPACE = 'lens';
|
|
uint256 internal immutable NAMESPACE_LENGTH = bytes(NAMESPACE).length;
|
|
uint256 internal constant SEPARATOR_LENGTH = 1; // bytes('.').length;
|
|
bytes32 internal constant NAMESPACE_HASH = keccak256(bytes(NAMESPACE));
|
|
|
|
modifier onlyOwnerOrHubOrWhitelistedProfileCreator() {
|
|
if (
|
|
msg.sender != OWNER && msg.sender != LENS_HUB && !ILensHub(LENS_HUB).isProfileCreatorWhitelisted(msg.sender)
|
|
) {
|
|
revert HandlesErrors.NotOwnerNorWhitelisted();
|
|
}
|
|
_;
|
|
}
|
|
|
|
mapping(uint256 tokenId => string localName) internal _localNames;
|
|
|
|
constructor(address owner, address lensHub) ERC721('', '') ImmutableOwnable(owner, lensHub) {}
|
|
|
|
function name() public pure override returns (string memory) {
|
|
return string.concat(symbol(), ' Handles');
|
|
}
|
|
|
|
function symbol() public pure override returns (string memory) {
|
|
return string.concat('.', NAMESPACE);
|
|
}
|
|
|
|
/**
|
|
* @dev See {IERC721Metadata-tokenURI}.
|
|
*/
|
|
function tokenURI(uint256 tokenId) public view override returns (string memory) {
|
|
_requireMinted(tokenId);
|
|
return HandleTokenURILib.getTokenURI(tokenId, _localNames[tokenId]);
|
|
}
|
|
|
|
/// @inheritdoc ILensHandles
|
|
function mintHandle(
|
|
address to,
|
|
string calldata localName
|
|
) external onlyOwnerOrHubOrWhitelistedProfileCreator returns (uint256) {
|
|
_validateLocalName(localName);
|
|
uint256 tokenId = getTokenId(localName);
|
|
_mint(to, tokenId);
|
|
_localNames[tokenId] = localName;
|
|
emit HandlesEvents.HandleMinted(localName, NAMESPACE, tokenId, to, block.timestamp);
|
|
return tokenId;
|
|
}
|
|
|
|
function burn(uint256 tokenId) external {
|
|
if (msg.sender != ownerOf(tokenId)) {
|
|
revert HandlesErrors.NotOwner();
|
|
}
|
|
_burn(tokenId);
|
|
delete _localNames[tokenId];
|
|
}
|
|
|
|
function exists(uint256 tokenId) external view returns (bool) {
|
|
return _exists(tokenId);
|
|
}
|
|
|
|
function getNamespace() external pure returns (string memory) {
|
|
return NAMESPACE;
|
|
}
|
|
|
|
function getNamespaceHash() external pure returns (bytes32) {
|
|
return NAMESPACE_HASH;
|
|
}
|
|
|
|
function getLocalName(uint256 tokenId) public view returns (string memory) {
|
|
return _localNames[tokenId];
|
|
}
|
|
|
|
function getHandle(uint256 tokenId) public view returns (string memory) {
|
|
return string.concat(_localNames[tokenId], '.', NAMESPACE);
|
|
}
|
|
|
|
function getTokenId(string memory localName) public pure returns (uint256) {
|
|
return uint256(keccak256(bytes(localName)));
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
/// INTERNAL FUNCTIONS ///
|
|
//////////////////////////////////////
|
|
|
|
function _validateLocalName(string memory localName) internal view {
|
|
uint256 localNameLength = bytes(localName).length;
|
|
if (localNameLength == 0 || localNameLength + SEPARATOR_LENGTH + NAMESPACE_LENGTH > MAX_HANDLE_LENGTH) {
|
|
revert HandlesErrors.HandleLengthInvalid();
|
|
}
|
|
|
|
bytes1 firstByte = bytes(localName)[0];
|
|
if (firstByte == '-' || firstByte == '_') {
|
|
revert HandlesErrors.HandleFirstCharInvalid();
|
|
}
|
|
|
|
uint256 i;
|
|
while (i < localNameLength) {
|
|
if (bytes(localName)[i] == '.') {
|
|
revert HandlesErrors.HandleContainsInvalidCharacters();
|
|
}
|
|
unchecked {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|