Blockchain / Global / Address

Contract

0x59B9Ac688e39A14b938AC8C3269db66D8aDB9aF6

Balance
0 SOMI
Transactions sent
1
Type
Contract · 24,072 bytes of code

ZNSRegistry Verified

Compiler
solc 0.8.21+commit.d9974bed
Language
Solidity
Optimization
Yes, 200 runs
EVM version
paris
License
—
Verified
2026-10-12

contracts/ZNSRegistryURI.sol

and 21 more files

Solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.21;

pragma abicoder v2;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import { ZNSOracle } from "./ZNSOracle.sol";
import { ZNSGiftCard } from "./ZNSGiftCard.sol";

contract ZNSRegistry is
	ERC721URIStorage,
	Pausable,
	ReentrancyGuard,
	AccessControl
{
	using Strings for uint256;

	/*//////////////////////////////////////////////////////////////
                            INITIALIZATION
    //////////////////////////////////////////////////////////////*/
	uint256 public tokenID;

	bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
	string public tld;

	string public baseUri;

	address[] profitSharingPartners = [
		0xD00c70F9b78C63a36519C488F862DF95b7A73d90
	];

	uint256[] profitSharesOfPartners = [10000];
	address public oracle;
	address public giftCard;

	struct RegistryData {
		address owner;
		string domainName;
		uint16 lengthOfDomain;
		uint256 expirationDate;
	}

	struct UserData {
		uint256[] ownedGiftCards;
		uint256 credits;
	}

	struct UserConfig {
		uint256 primaryDomain;
		uint256[] allOwnedDomains;
		uint256 numberOfReferrals;
		uint256 totalEarnings;
	}

	enum domainStatus {
		AVAILABLE,
		REGISTERED,
		EXPIRED
	}

	mapping(uint256 => RegistryData) internal registryLookup;
	mapping(address => UserConfig) internal userLookup;
	mapping(string => uint256) public domainLookup;
	mapping(uint256 => string) public idToDomain;
	mapping(string => bool) public protectedDomains;
	mapping(address => uint256) public partnerReferrals;
	mapping(uint256 => uint256) public mintToExpire;

	uint256[] public totalRegisteredDomains;

	uint256[5] internal domainPricing = [990e18, 490e18, 90e18, 20e18, 8e18];
	uint256[5] internal renewPricing = [99e18, 49e18, 9e18, 2e18, 8e17];

	// 1 invites - 5%
	// 10 invites - 10-%
	// 30 invites - 15%
	// 60 invites - 20%
	// 100 invites - 25%
	uint256[5] referTicks = [500, 1000, 1500, 2000, 2500];

	constructor(
		address _oracle,
		address _giftCard,
		string memory _symbol,
		string memory _tld
	) ERC721("ZNS Connect", _symbol) {
		tokenID++;
		_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
		_grantRole(MAINTAINER_ROLE, msg.sender);

		oracle = _oracle;
		giftCard = _giftCard;

		tld = _tld;

		baseUri = "https://api.znsconnect.io/v1/metadata";
	}

	/*//////////////////////////////////////////////////////////////
                            CUSTOM MODIFIERS
    //////////////////////////////////////////////////////////////*/

	modifier onlyMaintainer() {
		require(
			hasRole(MAINTAINER_ROLE, msg.sender),
			"maintainer role required"
		);
		_;
	}

	modifier onlyAdmin() {
		require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "admin role required");
		_;
	}

	/*//////////////////////////////////////////////////////////////
                            CUSTOM ERRORS
    //////////////////////////////////////////////////////////////*/

	error InvalidLength();
	error AlreadyRegistered();
	error SelfReferral();
	error DomainExpired();
	error NotRegistered();
	error cannotBeMoreThan100Percent();
	error InvalidAddress();
	error LengthsDoNotMatch();
	error NoCredits();
	error NotOwner();
	error NotEnoughCredits();
	error RefferalEarningCannotBeCalculated();
	error DomainNotExpired();
	error NotEnoughNativeTokenPaid();
	error DomainIn30dayPeriod();
	error PriceCannotBeZero();
	error DomainExpiredButNotBurned();
	error InvalidExpiry();
	error DomainIsProtected();
	error AmountMoreThanShare();
	error InvalidDomainName();

	/*//////////////////////////////////////////////////////////////
                            CUSTOM EVENTS
    //////////////////////////////////////////////////////////////*/

	event MintedDomain(
		string domainName,
		uint256 indexed tokenId,
		address indexed owner,
		uint256 indexed expiry
	);
	event PrimaryDomainSet(
		uint256 indexed tokenId,
		address indexed owner,
		string domainName
	);
	event RenewedDomain(
		uint256 indexed tokenId,
		uint256 indexed expiry,
		string domainName
	);
	event TransferredDomain(
		string domainName,
		uint256 indexed tokenId,
		address indexed from,
		address indexed to
	);

	/*//////////////////////////////////////////////////////////////
                            PUBLIC READ FUNCTIONS
    //////////////////////////////////////////////////////////////*/

	function registryLookupByName(
		string memory domainName
	) external view returns (RegistryData memory) {
		if (checkDomainStatus(domainLookup[domainName]) == domainStatus.EXPIRED)
			revert DomainExpired();
		return registryLookup[domainLookup[domainName]];
	}

	function registryLookupById(
		uint256 tokenId
	) external view returns (RegistryData memory) {
		if (checkDomainStatus(tokenId) == domainStatus.EXPIRED)
			revert DomainExpired();
		return registryLookup[tokenId];
	}

	function checkDomainStatus(
		uint256 tokenId
	) public view returns (domainStatus status) {
		RegistryData memory _registryData = registryLookup[tokenId];
		if (
			_registryData.owner == address(0) &&
			_registryData.expirationDate == 0 &&
			_registryData.lengthOfDomain == 0
		) return domainStatus.AVAILABLE;
		else if (
			_registryData.owner != address(0) &&
			_registryData.expirationDate > block.timestamp
		) return domainStatus.REGISTERED;
		else if (
			_registryData.owner != address(0) &&
			_registryData.expirationDate < block.timestamp
		) return domainStatus.EXPIRED;
	}

	function priceToRegister(uint16 len) public view returns (uint256) {
		uint256 currentUSDPrice = getOraclePrice();
		if (len <= 0 || len > 24) revert InvalidLength();
		if (len == 1) return (domainPricing[0] * 1e18) / currentUSDPrice;
		else if (len == 2) return (domainPricing[1] * 1e18) / currentUSDPrice;
		else if (len == 3) return (domainPricing[2] * 1e18) / currentUSDPrice;
		else if (len == 4) return (domainPricing[3] * 1e18) / currentUSDPrice;
		else if (len >= 5 && len <= 24)
			return (domainPricing[4] * 1e18) / currentUSDPrice;
		else revert InvalidLength();
	}

	function priceToRenew(uint16 len) public view returns (uint256) {
		uint256 currentUSDPrice = getOraclePrice();
		if (len <= 0 || len > 24) revert InvalidLength();

		if (len == 1) return (renewPricing[0] * 1e18) / currentUSDPrice;
		else if (len == 2) return (renewPricing[1] * 1e18) / currentUSDPrice;
		else if (len == 3) return (renewPricing[2] * 1e18) / currentUSDPrice;
		else if (len == 4) return (renewPricing[3] * 1e18) / currentUSDPrice;
		else if (len >= 5 && len <= 24)
			return (renewPricing[4] * 1e18) / currentUSDPrice;
		else revert InvalidLength();
	}

	function userLookupByAddress(
		address user
	) external view returns (UserConfig memory) {
		return userLookup[user];
	}

	function getOraclePrice() public view returns (uint256) {
		return ZNSOracle(oracle).priceToUSD();
	}

	function getTotalRegisteredDomains()
		external
		view
		returns (uint256[] memory)
	{
		return totalRegisteredDomains;
	}

	/*//////////////////////////////////////////////////////////////
                            ADMIN WRITE FUNCTIONS
    //////////////////////////////////////////////////////////////*/
	function setPartnerReferral(
		address referral,
		uint sharePercent
	) public onlyAdmin nonReentrant whenNotPaused {
		if (sharePercent > 10000) {
			revert cannotBeMoreThan100Percent();
		}
		partnerReferrals[referral] = sharePercent;
	}

	function setProfitSharingData(
		address[] memory _partners,
		uint256[] memory _percentages
	) public onlyAdmin nonReentrant whenNotPaused {
		if (_partners.length != _percentages.length) {
			revert LengthsDoNotMatch();
		}
		uint256 sum;
		for (uint256 i = 0; i < _percentages.length; i++) {
			sum += _percentages[i];
			if (isInvalidAddress(_partners[i])) revert InvalidAddress();
		}
		if (sum > 10000) {
			revert cannotBeMoreThan100Percent();
		}
		profitSharingPartners = _partners;
		profitSharesOfPartners = _percentages;
	}

	function setOracle(address _oracleAddress) public onlyAdmin nonReentrant {
		if (isInvalidAddress(_oracleAddress)) revert InvalidAddress();
		oracle = _oracleAddress;
	}

	function setGiftCard(
		address _giftCardAddress
	) public onlyAdmin nonReentrant {
		if (isInvalidAddress(_giftCardAddress)) revert InvalidAddress();
		giftCard = _giftCardAddress;
	}

	function setReferTicks(
		uint256[5] memory _ticks
	) public onlyAdmin nonReentrant whenNotPaused {
		for (uint256 i = 0; i < 5; i++) {
			if (_ticks[i] > 10000) {
				revert cannotBeMoreThan100Percent();
			}
		}
		referTicks = _ticks;
	}

	function setDomainPricing(
		uint256[5] memory _domainPricing
	) external onlyAdmin nonReentrant whenNotPaused {
		if (_domainPricing.length != 5) revert InvalidLength();
		for (uint256 i = 0; i < _domainPricing.length; i++) {
			if (_domainPricing[i] == 0) revert PriceCannotBeZero();
		}
		domainPricing = _domainPricing;
	}

	function setRenewPricing(
		uint256[5] memory _renewPricing
	) external onlyAdmin nonReentrant {
		if (_renewPricing.length != 5) revert InvalidLength();
		for (uint256 i = 0; i < _renewPricing.length; i++) {
			if (_renewPricing[i] == 0) revert PriceCannotBeZero();
		}
		renewPricing = _renewPricing;
	}

	function adminWithdraw() public onlyAdmin nonReentrant {
		payable(msg.sender).transfer(address(this).balance);
	}

	function adminRegisterDomains(
		address[] memory owners,
		string[] memory domainNames,
		uint256[] memory expiries
	) public onlyAdmin nonReentrant whenNotPaused {
		uint16[] memory lengthsOfDomains = new uint16[](domainNames.length);

		for (uint256 i = 0; i < domainNames.length; i++) {
			uint16 lengthOfDomain = uint16(strlen(domainNames[i]));
			lengthsOfDomains[i] = lengthOfDomain;
		}

		mintDomains(owners, domainNames, lengthsOfDomains, expiries);
	}

	function pause() external onlyAdmin nonReentrant {
		_pause();
	}

	function unpause() external onlyAdmin nonReentrant {
		_unpause();
	}

	function protectDomains(
		string[] memory domainNames,
		bool[] memory isProtectedValues
	) external onlyAdmin nonReentrant whenNotPaused {
		if (domainNames.length != isProtectedValues.length) {
			revert LengthsDoNotMatch();
		}
		for (uint256 i = 0; i < domainNames.length; i++) {
			protectedDomains[domainNames[i]] = isProtectedValues[i];
		}
	}

	function burnExpiredDomains(
		uint256[] memory tokenIds
	) external onlyMaintainer nonReentrant whenNotPaused {
		for (uint256 i = 0; i < tokenIds.length; i++) {
			uint256 tokenId = tokenIds[i];
			if (registryLookup[tokenId].expirationDate > block.timestamp)
				revert DomainNotExpired();

			if (
				registryLookup[tokenId].expirationDate + 30 days >
				block.timestamp
			) revert DomainIn30dayPeriod();
			maintainerBurn(tokenId);
		}
	}

	function setBaseUri(string memory _baseUri) public onlyAdmin {
		baseUri = _baseUri;
	}

	function setTld(string memory _tld) public onlyAdmin {
		tld = _tld;
	}

	function transferAdminRole(address _newAdmin) public onlyAdmin {
		_grantRole(DEFAULT_ADMIN_ROLE, _newAdmin);
	}

	function transferMaintainerRole(address _newMaintainer) public onlyAdmin {
		_grantRole(MAINTAINER_ROLE, _newMaintainer);
	}

	function transferAdminAndMaintainerRole(
		address _newOwner
	) public onlyAdmin {
		_grantRole(DEFAULT_ADMIN_ROLE, _newOwner);
		_grantRole(MAINTAINER_ROLE, _newOwner);
	}

	/*//////////////////////////////////////////////////////////////
                            PUBLIC WRITE FUNCTIONS
    //////////////////////////////////////////////////////////////*/

	function registerDomains(
		address[] memory owners,
		string[] memory domainNames,
		uint256[] memory expiries,
		address referral,
		uint256 credits
	) external payable nonReentrant whenNotPaused {
		uint256 totalPrice;
		uint16[] memory lengthsOfDomains = new uint16[](domainNames.length);

		for (uint256 i = 0; i < domainNames.length; i++) {
			uint256 expiry = expiries[i];
			string memory domainName = domainNames[i];
			if (!isValidDomainName(domainName)) {
				revert InvalidDomainName();
			}
			if (protectedDomains[domainName]) revert DomainIsProtected();
			uint16 lengthOfDomain = uint16(strlen(domainName));
			lengthsOfDomains[i] = lengthOfDomain;

			if (!isValidLength(lengthOfDomain)) revert InvalidLength();
			if (referral == msg.sender) revert SelfReferral();

			uint256 price = priceToRegister(lengthOfDomain);
			totalPrice += price;

			if (expiry > 1) {
				totalPrice += priceToRenew(lengthOfDomain) * (expiry - 1);
			}
		}

		if (credits > 0) {
			if (ZNSGiftCard(giftCard).getUserCredits(msg.sender) < credits) {
				revert NotEnoughCredits();
			}
			uint256 creditValue = getValueFromCredits(credits);
			ZNSGiftCard(giftCard).registryBurnCredits(msg.sender, credits);
			totalPrice -= creditValue;
		}

		if (msg.value < totalPrice) revert NotEnoughNativeTokenPaid();

		uint256 earnings = totalPrice;

		if (referral != address(0)) {
			uint256 referralBand = getReferralBand(referral);
			uint256 referralInBIPS = calculateActualFromBIPS(
				totalPrice,
				referralBand
			);
			userLookup[referral].numberOfReferrals += domainNames.length;
			userLookup[referral].totalEarnings += referralInBIPS;
			payable(referral).transfer(referralInBIPS);

			earnings -= referralInBIPS;
		}
		for (uint256 i = 0; i < profitSharingPartners.length; i++) {
			payable(profitSharingPartners[i]).transfer(
				calculateActualFromBIPS(earnings, profitSharesOfPartners[i])
			);
		}
		mintDomains(owners, domainNames, lengthsOfDomains, expiries);
	}

	function renewDomain(
		uint256 _tokenId,
		uint256 _years
	) external payable nonReentrant whenNotPaused {
		if (registryLookup[_tokenId].owner != msg.sender) revert NotOwner();
		if (_years == 0) revert InvalidExpiry();
		uint256 price = priceToRenew(registryLookup[_tokenId].lengthOfDomain) *
			_years;
		if (msg.value < price) revert NotEnoughNativeTokenPaid();
		for (uint256 i = 0; i < profitSharingPartners.length; i++) {
			payable(profitSharingPartners[i]).transfer(
				calculateActualFromBIPS(price, profitSharesOfPartners[i])
			);
		}
		registryLookup[_tokenId].expirationDate += 365 days * _years;
		emit RenewedDomain(
			_tokenId,
			registryLookup[_tokenId].expirationDate,
			addTLD(registryLookup[_tokenId].domainName)
		);
	}

	function setPrimaryDomain(
		uint256 _tokenId
	) external nonReentrant whenNotPaused {
		address owner = registryLookup[_tokenId].owner;
		if (owner != msg.sender) revert NotOwner();
		userLookup[owner].primaryDomain = _tokenId;
		emit PrimaryDomainSet(
			_tokenId,
			owner,
			addTLD(registryLookup[_tokenId].domainName)
		);
	}

	function burnDomain(uint256 _tokenId) external nonReentrant whenNotPaused {
		address owner = registryLookup[_tokenId].owner;
		if (owner != msg.sender) revert NotOwner();
		uint256[] memory ownedDomains = userLookup[owner].allOwnedDomains;
		uint256[] memory newOwnedDomains = new uint256[](
			ownedDomains.length - 1
		);
		uint256 counter = 0;
		for (uint256 i = 0; i < ownedDomains.length; i++) {
			if (ownedDomains[i] != _tokenId) {
				newOwnedDomains[counter] = ownedDomains[i];
				counter++;
			}
		}
		userLookup[owner].allOwnedDomains = newOwnedDomains;
		if (
			newOwnedDomains.length > 0 &&
			userLookup[owner].primaryDomain == _tokenId
		) {
			userLookup[owner].primaryDomain = newOwnedDomains[0];
			emit PrimaryDomainSet(
				newOwnedDomains[0],
				owner,
				addTLD(registryLookup[newOwnedDomains[0]].domainName)
			);
		} else {
			userLookup[owner].primaryDomain = 0;
			emit PrimaryDomainSet(0, owner, "");
		}
		delete registryLookup[_tokenId];
		_burn(_tokenId);

		clearDomain(_tokenId);
	}

	/*//////////////////////////////////////////////////////////////
                            INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

	function addTLD(
		string memory domainName
	) internal view returns (string memory) {
		return string.concat(domainName, string.concat(".", tld));
	}

	function getValueFromCredits(
		uint256 credits
	) internal view returns (uint256) {
		uint256 currentUSDPrice = getOraclePrice();
		return (credits * 1e18) / currentUSDPrice;
	}

	function isValidDomainName(
		string memory domainName
	) internal pure returns (bool) {
		bytes memory domainBytes = bytes(domainName);
		for (uint i = 0; i < domainBytes.length; i++) {
			// Check for lowercase letters, numbers, and the "-" character
			if (
				!(domainBytes[i] >= 0x30 && domainBytes[i] <= 0x39) && // 0-9
				!(domainBytes[i] >= 0x61 && domainBytes[i] <= 0x7A) && // a-z
				!(domainBytes[i] == 0x2D) && // "-" character
				!(domainBytes[i] > 0x7F) // Allow non-ASCII characters (basic check for emojis and other languages)
			) {
				// If the character is not a lowercase letter, number, "-", or > 0x7F (basic non-ASCII),
				// then it's invalid based on our criteria.
				return false;
			}
		}
		// Passed all checks
		return true;
	}

	function createTokenURI(
		uint256 _tokenId
	) internal view returns (string memory) {
		string memory tokenId = _tokenId.toString();

		uint256 id;
		assembly {
			id := chainid()
		}

		string memory tokenUri = string(
			abi.encodePacked(baseUri, "/", id.toString(), "/", tokenId)
		);

		return tokenUri;
	}

	function strlen(string memory s) internal pure returns (uint256) {
		uint256 len;
		uint256 i = 0;
		uint256 bytelength = bytes(s).length;
		for (len = 0; i < bytelength; len++) {
			bytes1 b = bytes(s)[i];
			if (b < 0x80) {
				i += 1;
			} else if (b < 0xE0) {
				i += 2;
			} else if (b < 0xF0) {
				i += 3;
			} else if (b < 0xF8) {
				i += 4;
			} else if (b < 0xFC) {
				i += 5;
			} else {
				i += 6;
			}
		}
		return len;
	}

	function isInvalidAddress(address _address) internal view returns (bool) {
		return _address == address(this) || _address == address(0);
	}

	function isValidLength(uint16 len) internal pure returns (bool) {
		return len > 0 && len <= 24;
	}

	function getReferralBand(address referral) public view returns (uint256) {
		if (partnerReferrals[referral] != 0) {
			return partnerReferrals[referral];
		}
		uint256 numberOfReferrals = userLookup[referral].numberOfReferrals;
		if (numberOfReferrals >= 0 && numberOfReferrals < 10)
			return referTicks[0];
		else if (numberOfReferrals >= 10 && numberOfReferrals < 30)
			return referTicks[1];
		else if (numberOfReferrals >= 30 && numberOfReferrals < 60)
			return referTicks[2];
		else if (numberOfReferrals >= 60 && numberOfReferrals < 100)
			return referTicks[3];
		else if (numberOfReferrals >= 100) return referTicks[4];
		else revert RefferalEarningCannotBeCalculated();
	}

	function calculateActualFromBIPS(
		uint256 price,
		uint256 bips
	) public pure returns (uint256) {
		return (price * bips) / 10000;
	}

	function mintDomains(
		address[] memory owners,
		string[] memory domainNames,
		uint16[] memory lengthsOfDomains,
		uint256[] memory expiries
	) internal whenNotPaused {
		if (
			domainNames.length != owners.length ||
			owners.length != expiries.length ||
			lengthsOfDomains.length != owners.length
		) {
			revert LengthsDoNotMatch();
		}

		uint256[] memory newTokenIds = new uint256[](domainNames.length);
		for (uint256 i = 0; i < domainNames.length; i++) {
			address owner = owners[i];
			string memory domainName = domainNames[i];
			uint256 expiry = expiries[i];
			uint16 lengthOfDomain = lengthsOfDomains[i];
			uint256 newRecordId = tokenID;
			newTokenIds[i] = newRecordId;

			if (isInvalidAddress(owners[i])) revert InvalidAddress();
			if (
				checkDomainStatus(domainLookup[domainName]) ==
				domainStatus.REGISTERED
			) revert AlreadyRegistered();
			if (
				checkDomainStatus(domainLookup[domainName]) ==
				domainStatus.EXPIRED
			) revert DomainExpiredButNotBurned();

			unchecked {
				tokenID++;
			}

			registryLookup[newRecordId] = RegistryData({
				owner: owner,
				domainName: domainName,
				lengthOfDomain: lengthOfDomain,
				expirationDate: block.timestamp + (365 days * expiry)
			});
			userLookup[owner].allOwnedDomains.push(newRecordId);
			domainLookup[domainName] = newRecordId;

			if (userLookup[owner].primaryDomain == 0) {
				userLookup[owner].primaryDomain = newRecordId;
				emit PrimaryDomainSet(newRecordId, owner, addTLD(domainName));
			}

			_safeMint(owner, newRecordId);
			_setTokenURI(newRecordId, createTokenURI(newRecordId));

			totalRegisteredDomains.push(newRecordId);
			idToDomain[newRecordId] = domainName;
			mintToExpire[newRecordId] = expiry;

			emit MintedDomain(
				addTLD(domainName),
				newRecordId,
				owner,
				registryLookup[newRecordId].expirationDate
			);
		}
	}

	function _beforeTokenTransfer(
		address from,
		address to,
		uint256 firstTokenId,
		uint256 batchSize
	) internal virtual override {
		super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
		if (from != address(0) && to != address(0)) {
			if (checkDomainStatus(firstTokenId) == domainStatus.EXPIRED)
				revert DomainExpired();
			uint256[] memory ownedDomains = userLookup[from].allOwnedDomains;
			uint256[] memory newOwnedDomains = new uint256[](
				ownedDomains.length - batchSize
			);
			uint256 counter = 0;
			for (uint256 i = 0; i < ownedDomains.length; i++) {
				if (ownedDomains[i] != firstTokenId) {
					newOwnedDomains[counter] = ownedDomains[i];
					counter++;
				}
			}
			userLookup[from].allOwnedDomains = newOwnedDomains;
			if (
				newOwnedDomains.length > 0 &&
				userLookup[from].primaryDomain == firstTokenId
			) {
				userLookup[from].primaryDomain = newOwnedDomains[0];
				emit PrimaryDomainSet(
					newOwnedDomains[0],
					from,
					addTLD(registryLookup[newOwnedDomains[0]].domainName)
				);
			} else {
				userLookup[from].primaryDomain = 0;
				emit PrimaryDomainSet(0, from, "");
			}
			userLookup[to].allOwnedDomains.push(firstTokenId);
			if (userLookup[to].primaryDomain == 0) {
				userLookup[to].primaryDomain = firstTokenId;
				emit PrimaryDomainSet(
					firstTokenId,
					to,
					addTLD(registryLookup[firstTokenId].domainName)
				);
			}
			registryLookup[firstTokenId].owner = to;
		}
		emit TransferredDomain(
			addTLD(registryLookup[firstTokenId].domainName),
			firstTokenId,
			from,
			to
		);
	}

	function maintainerBurn(uint256 tokenId) internal {
		if (tokenId == 0) revert NotRegistered();
		address owner = registryLookup[tokenId].owner;
		uint256[] memory ownedDomains = userLookup[owner].allOwnedDomains;
		uint256[] memory newOwnedDomains = new uint256[](
			ownedDomains.length - 1
		);
		uint256 counter = 0;
		for (uint256 i = 0; i < ownedDomains.length; i++) {
			if (ownedDomains[i] != tokenId) {
				newOwnedDomains[counter] = ownedDomains[i];
				counter++;
			}
		}
		userLookup[owner].allOwnedDomains = newOwnedDomains;
		if (
			newOwnedDomains.length > 0 &&
			userLookup[owner].primaryDomain == tokenId
		) {
			userLookup[owner].primaryDomain = newOwnedDomains[0];
			emit PrimaryDomainSet(
				newOwnedDomains[0],
				owner,
				addTLD(registryLookup[newOwnedDomains[0]].domainName)
			);
		} else {
			userLookup[owner].primaryDomain = 0;
			emit PrimaryDomainSet(0, owner, "");
		}
		delete registryLookup[tokenId];
		_burn(tokenId);

		clearDomain(tokenId);
	}

	function clearDomain(uint256 _tokenId) internal {
		idToDomain[_tokenId] = "";

		for (uint256 i = 0; i < totalRegisteredDomains.length; i++) {
			if (totalRegisteredDomains[i] == _tokenId) {
				totalRegisteredDomains[i] = totalRegisteredDomains[
					totalRegisteredDomains.length - 1
				];
				totalRegisteredDomains.pop();
			}
		}
	}

	/*//////////////////////////////////////////////////////////////
                            DEPENDANCY FUNCTIONS
    //////////////////////////////////////////////////////////////*/

	function supportsInterface(
		bytes4 interfaceId
	)
		public
		view
		virtual
		override(ERC721URIStorage, AccessControl)
		returns (bool)
	{
		return super.supportsInterface(interfaceId);
	}
}

Other files

  • @openzeppelin/contracts/access/AccessControl.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IAccessControl.sol";
    import "../utils/Context.sol";
    import "../utils/Strings.sol";
    import "../utils/introspection/ERC165.sol";
    
    /**
     * @dev Contract module that allows children to implement role-based access
     * control mechanisms. This is a lightweight version that doesn't allow enumerating role
     * members except through off-chain means by accessing the contract event logs. Some
     * applications may benefit from on-chain enumerability, for those cases see
     * {AccessControlEnumerable}.
     *
     * Roles are referred to by their `bytes32` identifier. These should be exposed
     * in the external API and be unique. The best way to achieve this is by
     * using `public constant` hash digests:
     *
     * ```solidity
     * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
     * ```
     *
     * Roles can be used to represent a set of permissions. To restrict access to a
     * function call, use {hasRole}:
     *
     * ```solidity
     * function foo() public {
     *     require(hasRole(MY_ROLE, msg.sender));
     *     ...
     * }
     * ```
     *
     * Roles can be granted and revoked dynamically via the {grantRole} and
     * {revokeRole} functions. Each role has an associated admin role, and only
     * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
     *
     * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
     * that only accounts with this role will be able to grant or revoke other
     * roles. More complex role relationships can be created by using
     * {_setRoleAdmin}.
     *
     * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
     * grant and revoke this role. Extra precautions should be taken to secure
     * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
     * to enforce additional security measures for this role.
     */
    abstract contract AccessControl is Context, IAccessControl, ERC165 {
        struct RoleData {
            mapping(address => bool) members;
            bytes32 adminRole;
        }
    
        mapping(bytes32 => RoleData) private _roles;
    
        bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
    
        /**
         * @dev Modifier that checks that an account has a specific role. Reverts
         * with a standardized message including the required role.
         *
         * The format of the revert reason is given by the following regular expression:
         *
         *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
         *
         * _Available since v4.1._
         */
        modifier onlyRole(bytes32 role) {
            _checkRole(role);
            _;
        }
    
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
            return _roles[role].members[account];
        }
    
        /**
         * @dev Revert with a standard message if `_msgSender()` is missing `role`.
         * Overriding this function changes the behavior of the {onlyRole} modifier.
         *
         * Format of the revert message is described in {_checkRole}.
         *
         * _Available since v4.6._
         */
        function _checkRole(bytes32 role) internal view virtual {
            _checkRole(role, _msgSender());
        }
    
        /**
         * @dev Revert with a standard message if `account` is missing `role`.
         *
         * The format of the revert reason is given by the following regular expression:
         *
         *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
         */
        function _checkRole(bytes32 role, address account) internal view virtual {
            if (!hasRole(role, account)) {
                revert(
                    string(
                        abi.encodePacked(
                            "AccessControl: account ",
                            Strings.toHexString(account),
                            " is missing role ",
                            Strings.toHexString(uint256(role), 32)
                        )
                    )
                );
            }
        }
    
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
            return _roles[role].adminRole;
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         *
         * May emit a {RoleGranted} event.
         */
        function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
            _grantRole(role, account);
        }
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         *
         * May emit a {RoleRevoked} event.
         */
        function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
            _revokeRole(role, account);
        }
    
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been revoked `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         *
         * May emit a {RoleRevoked} event.
         */
        function renounceRole(bytes32 role, address account) public virtual override {
            require(account == _msgSender(), "AccessControl: can only renounce roles for self");
    
            _revokeRole(role, account);
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event. Note that unlike {grantRole}, this function doesn't perform any
         * checks on the calling account.
         *
         * May emit a {RoleGranted} event.
         *
         * [WARNING]
         * ====
         * This function should only be called from the constructor when setting
         * up the initial roles for the system.
         *
         * Using this function in any other way is effectively circumventing the admin
         * system imposed by {AccessControl}.
         * ====
         *
         * NOTE: This function is deprecated in favor of {_grantRole}.
         */
        function _setupRole(bytes32 role, address account) internal virtual {
            _grantRole(role, account);
        }
    
        /**
         * @dev Sets `adminRole` as ``role``'s admin role.
         *
         * Emits a {RoleAdminChanged} event.
         */
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
            bytes32 previousAdminRole = getRoleAdmin(role);
            _roles[role].adminRole = adminRole;
            emit RoleAdminChanged(role, previousAdminRole, adminRole);
        }
    
        /**
         * @dev Grants `role` to `account`.
         *
         * Internal function without access restriction.
         *
         * May emit a {RoleGranted} event.
         */
        function _grantRole(bytes32 role, address account) internal virtual {
            if (!hasRole(role, account)) {
                _roles[role].members[account] = true;
                emit RoleGranted(role, account, _msgSender());
            }
        }
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * Internal function without access restriction.
         *
         * May emit a {RoleRevoked} event.
         */
        function _revokeRole(bytes32 role, address account) internal virtual {
            if (hasRole(role, account)) {
                _roles[role].members[account] = false;
                emit RoleRevoked(role, account, _msgSender());
            }
        }
    }
    
  • @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @title ERC721 token receiver interface
     * @dev Interface for any contract that wants to support safeTransfers
     * from ERC721 asset contracts.
     */
    interface IERC721Receiver {
        /**
         * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
         * by `operator` from `from`, this function is called.
         *
         * It must return its Solidity selector to confirm the token transfer.
         * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
         *
         * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
         */
        function onERC721Received(
            address operator,
            address from,
            uint256 tokenId,
            bytes calldata data
        ) external returns (bytes4);
    }
    
  • @openzeppelin/contracts/access/IAccessControl.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev External interface of AccessControl declared to support ERC165 detection.
     */
    interface IAccessControl {
        /**
         * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
         *
         * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
         * {RoleAdminChanged} not being emitted signaling this.
         *
         * _Available since v3.1._
         */
        event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
    
        /**
         * @dev Emitted when `account` is granted `role`.
         *
         * `sender` is the account that originated the contract call, an admin role
         * bearer except when using {AccessControl-_setupRole}.
         */
        event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    
        /**
         * @dev Emitted when `account` is revoked `role`.
         *
         * `sender` is the account that originated the contract call:
         *   - if using `revokeRole`, it is the admin role bearer
         *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
         */
        event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
    
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) external view returns (bool);
    
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {AccessControl-_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) external view returns (bytes32);
    
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function grantRole(bytes32 role, address account) external;
    
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function revokeRole(bytes32 role, address account) external;
    
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been granted `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         */
        function renounceRole(bytes32 role, address account) external;
    }
    
  • @openzeppelin/contracts/interfaces/IERC165.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol)
    
    pragma solidity ^0.8.0;
    
    import "../utils/introspection/IERC165.sol";
    
  • @openzeppelin/contracts/interfaces/IERC4906.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4906.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC165.sol";
    import "./IERC721.sol";
    
    /// @title EIP-721 Metadata Update Extension
    interface IERC4906 is IERC165, IERC721 {
        /// @dev This event emits when the metadata of a token is changed.
        /// So that the third-party platforms such as NFT market could
        /// timely update the images and related attributes of the NFT.
        event MetadataUpdate(uint256 _tokenId);
    
        /// @dev This event emits when the metadata of a range of tokens is changed.
        /// So that the third-party platforms such as NFT market could
        /// timely update the images and related attributes of the NFTs.
        event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
    }
    
  • @openzeppelin/contracts/interfaces/IERC721.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "../token/ERC721/IERC721.sol";
    
  • @openzeppelin/contracts/security/Pausable.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
    
    pragma solidity ^0.8.0;
    
    import "../utils/Context.sol";
    
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract Pausable is Context {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
    
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
    
        bool private _paused;
    
        /**
         * @dev Initializes the contract in unpaused state.
         */
        constructor() {
            _paused = false;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            _requireNotPaused();
            _;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            _requirePaused();
            _;
        }
    
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
    
        /**
         * @dev Throws if the contract is paused.
         */
        function _requireNotPaused() internal view virtual {
            require(!paused(), "Pausable: paused");
        }
    
        /**
         * @dev Throws if the contract is not paused.
         */
        function _requirePaused() internal view virtual {
            require(paused(), "Pausable: not paused");
        }
    
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
    
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    }
    
  • @openzeppelin/contracts/security/ReentrancyGuard.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    abstract contract ReentrancyGuard {
        // Booleans are more expensive than uint256 or any type that takes up a full
        // word because each write operation emits an extra SLOAD to first read the
        // slot's contents, replace the bits taken up by the boolean, and then write
        // back. This is the compiler's defense against contract upgrades and
        // pointer aliasing, and it cannot be disabled.
    
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant _NOT_ENTERED = 1;
        uint256 private constant _ENTERED = 2;
    
        uint256 private _status;
    
        constructor() {
            _status = _NOT_ENTERED;
        }
    
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            _nonReentrantBefore();
            _;
            _nonReentrantAfter();
        }
    
        function _nonReentrantBefore() private {
            // On the first call to nonReentrant, _status will be _NOT_ENTERED
            require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
    
            // Any calls to nonReentrant after this point will fail
            _status = _ENTERED;
        }
    
        function _nonReentrantAfter() private {
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _status = _NOT_ENTERED;
        }
    
        /**
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
         * `nonReentrant` function in the call stack.
         */
        function _reentrancyGuardEntered() internal view returns (bool) {
            return _status == _ENTERED;
        }
    }
    
  • @openzeppelin/contracts/token/ERC721/ERC721.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC721.sol";
    import "./IERC721Receiver.sol";
    import "./extensions/IERC721Metadata.sol";
    import "../../utils/Address.sol";
    import "../../utils/Context.sol";
    import "../../utils/Strings.sol";
    import "../../utils/introspection/ERC165.sol";
    
    /**
     * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
     * the Metadata extension, but not including the Enumerable extension, which is available separately as
     * {ERC721Enumerable}.
     */
    contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
        using Address for address;
        using Strings for uint256;
    
        // Token name
        string private _name;
    
        // Token symbol
        string private _symbol;
    
        // Mapping from token ID to owner address
        mapping(uint256 => address) private _owners;
    
        // Mapping owner address to token count
        mapping(address => uint256) private _balances;
    
        // Mapping from token ID to approved address
        mapping(uint256 => address) private _tokenApprovals;
    
        // Mapping from owner to operator approvals
        mapping(address => mapping(address => bool)) private _operatorApprovals;
    
        /**
         * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
         */
        constructor(string memory name_, string memory symbol_) {
            _name = name_;
            _symbol = symbol_;
        }
    
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
            return
                interfaceId == type(IERC721).interfaceId ||
                interfaceId == type(IERC721Metadata).interfaceId ||
                super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev See {IERC721-balanceOf}.
         */
        function balanceOf(address owner) public view virtual override returns (uint256) {
            require(owner != address(0), "ERC721: address zero is not a valid owner");
            return _balances[owner];
        }
    
        /**
         * @dev See {IERC721-ownerOf}.
         */
        function ownerOf(uint256 tokenId) public view virtual override returns (address) {
            address owner = _ownerOf(tokenId);
            require(owner != address(0), "ERC721: invalid token ID");
            return owner;
        }
    
        /**
         * @dev See {IERC721Metadata-name}.
         */
        function name() public view virtual override returns (string memory) {
            return _name;
        }
    
        /**
         * @dev See {IERC721Metadata-symbol}.
         */
        function symbol() public view virtual override returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev See {IERC721Metadata-tokenURI}.
         */
        function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
            _requireMinted(tokenId);
    
            string memory baseURI = _baseURI();
            return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
        }
    
        /**
         * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
         * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
         * by default, can be overridden in child contracts.
         */
        function _baseURI() internal view virtual returns (string memory) {
            return "";
        }
    
        /**
         * @dev See {IERC721-approve}.
         */
        function approve(address to, uint256 tokenId) public virtual override {
            address owner = ERC721.ownerOf(tokenId);
            require(to != owner, "ERC721: approval to current owner");
    
            require(
                _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                "ERC721: approve caller is not token owner or approved for all"
            );
    
            _approve(to, tokenId);
        }
    
        /**
         * @dev See {IERC721-getApproved}.
         */
        function getApproved(uint256 tokenId) public view virtual override returns (address) {
            _requireMinted(tokenId);
    
            return _tokenApprovals[tokenId];
        }
    
        /**
         * @dev See {IERC721-setApprovalForAll}.
         */
        function setApprovalForAll(address operator, bool approved) public virtual override {
            _setApprovalForAll(_msgSender(), operator, approved);
        }
    
        /**
         * @dev See {IERC721-isApprovedForAll}.
         */
        function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
            return _operatorApprovals[owner][operator];
        }
    
        /**
         * @dev See {IERC721-transferFrom}.
         */
        function transferFrom(address from, address to, uint256 tokenId) public virtual override {
            //solhint-disable-next-line max-line-length
            require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
    
            _transfer(from, to, tokenId);
        }
    
        /**
         * @dev See {IERC721-safeTransferFrom}.
         */
        function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
            safeTransferFrom(from, to, tokenId, "");
        }
    
        /**
         * @dev See {IERC721-safeTransferFrom}.
         */
        function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
            require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
            _safeTransfer(from, to, tokenId, data);
        }
    
        /**
         * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
         * are aware of the ERC721 protocol to prevent tokens from being forever locked.
         *
         * `data` is additional data, it has no specified format and it is sent in call to `to`.
         *
         * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
         * implement alternative mechanisms to perform token transfer, such as signature-based.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
            _transfer(from, to, tokenId);
            require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
        }
    
        /**
         * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
         */
        function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
            return _owners[tokenId];
        }
    
        /**
         * @dev Returns whether `tokenId` exists.
         *
         * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
         *
         * Tokens start existing when they are minted (`_mint`),
         * and stop existing when they are burned (`_burn`).
         */
        function _exists(uint256 tokenId) internal view virtual returns (bool) {
            return _ownerOf(tokenId) != address(0);
        }
    
        /**
         * @dev Returns whether `spender` is allowed to manage `tokenId`.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
            address owner = ERC721.ownerOf(tokenId);
            return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
        }
    
        /**
         * @dev Safely mints `tokenId` and transfers it to `to`.
         *
         * Requirements:
         *
         * - `tokenId` must not exist.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function _safeMint(address to, uint256 tokenId) internal virtual {
            _safeMint(to, tokenId, "");
        }
    
        /**
         * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
         * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
         */
        function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
            _mint(to, tokenId);
            require(
                _checkOnERC721Received(address(0), to, tokenId, data),
                "ERC721: transfer to non ERC721Receiver implementer"
            );
        }
    
        /**
         * @dev Mints `tokenId` and transfers it to `to`.
         *
         * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
         *
         * Requirements:
         *
         * - `tokenId` must not exist.
         * - `to` cannot be the zero address.
         *
         * Emits a {Transfer} event.
         */
        function _mint(address to, uint256 tokenId) internal virtual {
            require(to != address(0), "ERC721: mint to the zero address");
            require(!_exists(tokenId), "ERC721: token already minted");
    
            _beforeTokenTransfer(address(0), to, tokenId, 1);
    
            // Check that tokenId was not minted by `_beforeTokenTransfer` hook
            require(!_exists(tokenId), "ERC721: token already minted");
    
            unchecked {
                // Will not overflow unless all 2**256 token ids are minted to the same owner.
                // Given that tokens are minted one by one, it is impossible in practice that
                // this ever happens. Might change if we allow batch minting.
                // The ERC fails to describe this case.
                _balances[to] += 1;
            }
    
            _owners[tokenId] = to;
    
            emit Transfer(address(0), to, tokenId);
    
            _afterTokenTransfer(address(0), to, tokenId, 1);
        }
    
        /**
         * @dev Destroys `tokenId`.
         * The approval is cleared when the token is burned.
         * This is an internal function that does not check if the sender is authorized to operate on the token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         *
         * Emits a {Transfer} event.
         */
        function _burn(uint256 tokenId) internal virtual {
            address owner = ERC721.ownerOf(tokenId);
    
            _beforeTokenTransfer(owner, address(0), tokenId, 1);
    
            // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
            owner = ERC721.ownerOf(tokenId);
    
            // Clear approvals
            delete _tokenApprovals[tokenId];
    
            unchecked {
                // Cannot overflow, as that would require more tokens to be burned/transferred
                // out than the owner initially received through minting and transferring in.
                _balances[owner] -= 1;
            }
            delete _owners[tokenId];
    
            emit Transfer(owner, address(0), tokenId);
    
            _afterTokenTransfer(owner, address(0), tokenId, 1);
        }
    
        /**
         * @dev Transfers `tokenId` from `from` to `to`.
         *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
         *
         * Requirements:
         *
         * - `to` cannot be the zero address.
         * - `tokenId` token must be owned by `from`.
         *
         * Emits a {Transfer} event.
         */
        function _transfer(address from, address to, uint256 tokenId) internal virtual {
            require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
            require(to != address(0), "ERC721: transfer to the zero address");
    
            _beforeTokenTransfer(from, to, tokenId, 1);
    
            // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
            require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
    
            // Clear approvals from the previous owner
            delete _tokenApprovals[tokenId];
    
            unchecked {
                // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
                // `from`'s balance is the number of token held, which is at least one before the current
                // transfer.
                // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
                // all 2**256 token ids to be minted, which in practice is impossible.
                _balances[from] -= 1;
                _balances[to] += 1;
            }
            _owners[tokenId] = to;
    
            emit Transfer(from, to, tokenId);
    
            _afterTokenTransfer(from, to, tokenId, 1);
        }
    
        /**
         * @dev Approve `to` to operate on `tokenId`
         *
         * Emits an {Approval} event.
         */
        function _approve(address to, uint256 tokenId) internal virtual {
            _tokenApprovals[tokenId] = to;
            emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
        }
    
        /**
         * @dev Approve `operator` to operate on all of `owner` tokens
         *
         * Emits an {ApprovalForAll} event.
         */
        function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
            require(owner != operator, "ERC721: approve to caller");
            _operatorApprovals[owner][operator] = approved;
            emit ApprovalForAll(owner, operator, approved);
        }
    
        /**
         * @dev Reverts if the `tokenId` has not been minted yet.
         */
        function _requireMinted(uint256 tokenId) internal view virtual {
            require(_exists(tokenId), "ERC721: invalid token ID");
        }
    
        /**
         * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
         * The call is not executed if the target address is not a contract.
         *
         * @param from address representing the previous owner of the given token ID
         * @param to target address that will receive the tokens
         * @param tokenId uint256 ID of the token to be transferred
         * @param data bytes optional data to send along with the call
         * @return bool whether the call correctly returned the expected magic value
         */
        function _checkOnERC721Received(
            address from,
            address to,
            uint256 tokenId,
            bytes memory data
        ) private returns (bool) {
            if (to.isContract()) {
                try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                    return retval == IERC721Receiver.onERC721Received.selector;
                } catch (bytes memory reason) {
                    if (reason.length == 0) {
                        revert("ERC721: transfer to non ERC721Receiver implementer");
                    } else {
                        /// @solidity memory-safe-assembly
                        assembly {
                            revert(add(32, reason), mload(reason))
                        }
                    }
                }
            } else {
                return true;
            }
        }
    
        /**
         * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
         * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
         *
         * Calling conditions:
         *
         * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
         * - When `from` is zero, the tokens will be minted for `to`.
         * - When `to` is zero, ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         * - `batchSize` is non-zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
    
        /**
         * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
         * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
         *
         * Calling conditions:
         *
         * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
         * - When `from` is zero, the tokens were minted for `to`.
         * - When `to` is zero, ``from``'s tokens were burned.
         * - `from` and `to` are never both zero.
         * - `batchSize` is non-zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
    
        /**
         * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
         *
         * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
         * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
         * that `ownerOf(tokenId)` is `a`.
         */
        // solhint-disable-next-line func-name-mixedcase
        function __unsafe_increaseBalance(address account, uint256 amount) internal {
            _balances[account] += amount;
        }
    }
    
  • @openzeppelin/contracts/token/ERC721/IERC721.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
    
    pragma solidity ^0.8.0;
    
    import "../../utils/introspection/IERC165.sol";
    
    /**
     * @dev Required interface of an ERC721 compliant contract.
     */
    interface IERC721 is IERC165 {
        /**
         * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
         */
        event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    
        /**
         * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
         */
        event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    
        /**
         * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
         */
        event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    
        /**
         * @dev Returns the number of tokens in ``owner``'s account.
         */
        function balanceOf(address owner) external view returns (uint256 balance);
    
        /**
         * @dev Returns the owner of the `tokenId` token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function ownerOf(uint256 tokenId) external view returns (address owner);
    
        /**
         * @dev Safely transfers `tokenId` token from `from` to `to`.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
    
        /**
         * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
         * are aware of the ERC721 protocol to prevent tokens from being forever locked.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function safeTransferFrom(address from, address to, uint256 tokenId) external;
    
        /**
         * @dev Transfers `tokenId` token from `from` to `to`.
         *
         * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
         * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
         * understand this adds an external call which potentially creates a reentrancy vulnerability.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must be owned by `from`.
         * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 tokenId) external;
    
        /**
         * @dev Gives permission to `to` to transfer `tokenId` token to another account.
         * The approval is cleared when the token is transferred.
         *
         * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
         *
         * Requirements:
         *
         * - The caller must own the token or be an approved operator.
         * - `tokenId` must exist.
         *
         * Emits an {Approval} event.
         */
        function approve(address to, uint256 tokenId) external;
    
        /**
         * @dev Approve or remove `operator` as an operator for the caller.
         * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
         *
         * Requirements:
         *
         * - The `operator` cannot be the caller.
         *
         * Emits an {ApprovalForAll} event.
         */
        function setApprovalForAll(address operator, bool approved) external;
    
        /**
         * @dev Returns the account approved for `tokenId` token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function getApproved(uint256 tokenId) external view returns (address operator);
    
        /**
         * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
         *
         * See {setApprovalForAll}
         */
        function isApprovedForAll(address owner, address operator) external view returns (bool);
    }
    
  • @openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721URIStorage.sol)
    
    pragma solidity ^0.8.0;
    
    import "../ERC721.sol";
    import "../../../interfaces/IERC4906.sol";
    
    /**
     * @dev ERC721 token with storage based token URI management.
     */
    abstract contract ERC721URIStorage is IERC4906, ERC721 {
        using Strings for uint256;
    
        // Optional mapping for token URIs
        mapping(uint256 => string) private _tokenURIs;
    
        /**
         * @dev See {IERC165-supportsInterface}
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) {
            return interfaceId == bytes4(0x49064906) || super.supportsInterface(interfaceId);
        }
    
        /**
         * @dev See {IERC721Metadata-tokenURI}.
         */
        function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
            _requireMinted(tokenId);
    
            string memory _tokenURI = _tokenURIs[tokenId];
            string memory base = _baseURI();
    
            // If there is no base URI, return the token URI.
            if (bytes(base).length == 0) {
                return _tokenURI;
            }
            // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
            if (bytes(_tokenURI).length > 0) {
                return string(abi.encodePacked(base, _tokenURI));
            }
    
            return super.tokenURI(tokenId);
        }
    
        /**
         * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
         *
         * Emits {MetadataUpdate}.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
            require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
            _tokenURIs[tokenId] = _tokenURI;
    
            emit MetadataUpdate(tokenId);
        }
    
        /**
         * @dev See {ERC721-_burn}. This override additionally checks to see if a
         * token-specific URI was set for the token, and if so, it deletes the token URI from
         * the storage mapping.
         */
        function _burn(uint256 tokenId) internal virtual override {
            super._burn(tokenId);
    
            if (bytes(_tokenURIs[tokenId]).length != 0) {
                delete _tokenURIs[tokenId];
            }
        }
    }
    
  • @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
    
    pragma solidity ^0.8.0;
    
    import "../IERC721.sol";
    
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721Metadata is IERC721 {
        /**
         * @dev Returns the token collection name.
         */
        function name() external view returns (string memory);
    
        /**
         * @dev Returns the token collection symbol.
         */
        function symbol() external view returns (string memory);
    
        /**
         * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
         */
        function tokenURI(uint256 tokenId) external view returns (string memory);
    }
    
  • @openzeppelin/contracts/utils/Address.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    
    pragma solidity ^0.8.1;
    
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         *
         * Furthermore, `isContract` will also return true if the target contract within
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
         * which only has an effect at the end of a transaction.
         * ====
         *
         * [IMPORTANT]
         * ====
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
         *
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
         * constructor.
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize/address.code.length, which returns 0
            // for contracts in construction, since the code is only stored at the end
            // of the constructor execution.
    
            return account.code.length > 0;
        }
    
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
    
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
    
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
    
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
        }
    
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
         *
         * _Available since v4.8._
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            if (success) {
                if (returndata.length == 0) {
                    // only check isContract if the call was successful and the return data is empty
                    // otherwise we already know that it was a contract
                    require(isContract(target), "Address: call to non-contract");
                }
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        /**
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason or using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                _revert(returndata, errorMessage);
            }
        }
    
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
    
  • @openzeppelin/contracts/utils/Context.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    
  • @openzeppelin/contracts/utils/Strings.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
    
    pragma solidity ^0.8.0;
    
    import "./math/Math.sol";
    import "./math/SignedMath.sol";
    
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = Math.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
    
        /**
         * @dev Converts a `int256` to its ASCII `string` decimal representation.
         */
        function toString(int256 value) internal pure returns (string memory) {
            return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, Math.log256(value) + 1);
            }
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
    
        /**
         * @dev Returns true if the two strings are equal.
         */
        function equal(string memory a, string memory b) internal pure returns (bool) {
            return keccak256(bytes(a)) == keccak256(bytes(b));
        }
    }
    
  • @openzeppelin/contracts/utils/introspection/ERC165.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
    
    pragma solidity ^0.8.0;
    
    import "./IERC165.sol";
    
    /**
     * @dev Implementation of the {IERC165} interface.
     *
     * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
     * for the additional interface id that will be supported. For example:
     *
     * ```solidity
     * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
     *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
     * }
     * ```
     *
     * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
     */
    abstract contract ERC165 is IERC165 {
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IERC165).interfaceId;
        }
    }
    
  • @openzeppelin/contracts/utils/introspection/IERC165.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Interface of the ERC165 standard, as defined in the
     * https://eips.ethereum.org/EIPS/eip-165[EIP].
     *
     * Implementers can declare support of contract interfaces, which can then be
     * queried by others ({ERC165Checker}).
     *
     * For an implementation, see {ERC165}.
     */
    interface IERC165 {
        /**
         * @dev Returns true if this contract implements the interface defined by
         * `interfaceId`. See the corresponding
         * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
         * to learn more about how these ids are created.
         *
         * This function call must use less than 30 000 gas.
         */
        function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }
    
  • @openzeppelin/contracts/utils/math/Math.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library Math {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
    
        /**
         * @dev Returns the largest of two numbers.
         */
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two numbers.
         */
        function min(uint256 a, uint256 b) internal pure returns (uint256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
    
        /**
         * @dev Returns the ceiling of the division of two numbers.
         *
         * This differs from standard division with `/` in that it rounds up instead
         * of rounding down.
         */
        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b - 1) / b can overflow on addition, so we distribute.
            return a == 0 ? 0 : (a - 1) / b + 1;
        }
    
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
    
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                    // The surrounding unchecked block does not change this fact.
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                    return prod0 / denominator;
                }
    
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1, "Math: mulDiv overflow");
    
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
    
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
    
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
    
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
    
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
    
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
    
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
    
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
    
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
    
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
    
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
    
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
    
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
    
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
    
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
    
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10 ** 64) {
                    value /= 10 ** 64;
                    result += 64;
                }
                if (value >= 10 ** 32) {
                    value /= 10 ** 32;
                    result += 32;
                }
                if (value >= 10 ** 16) {
                    value /= 10 ** 16;
                    result += 16;
                }
                if (value >= 10 ** 8) {
                    value /= 10 ** 8;
                    result += 8;
                }
                if (value >= 10 ** 4) {
                    value /= 10 ** 4;
                    result += 4;
                }
                if (value >= 10 ** 2) {
                    value /= 10 ** 2;
                    result += 2;
                }
                if (value >= 10 ** 1) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
            }
        }
    
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
    
        /**
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
            }
        }
    }
    
  • @openzeppelin/contracts/utils/math/SignedMath.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Standard signed math utilities missing in the Solidity language.
     */
    library SignedMath {
        /**
         * @dev Returns the largest of two signed numbers.
         */
        function max(int256 a, int256 b) internal pure returns (int256) {
            return a > b ? a : b;
        }
    
        /**
         * @dev Returns the smallest of two signed numbers.
         */
        function min(int256 a, int256 b) internal pure returns (int256) {
            return a < b ? a : b;
        }
    
        /**
         * @dev Returns the average of two signed numbers without overflow.
         * The result is rounded towards zero.
         */
        function average(int256 a, int256 b) internal pure returns (int256) {
            // Formula from the book "Hacker's Delight"
            int256 x = (a & b) + ((a ^ b) >> 1);
            return x + (int256(uint256(x) >> 255) & (a ^ b));
        }
    
        /**
         * @dev Returns the absolute unsigned value of a signed value.
         */
        function abs(int256 n) internal pure returns (uint256) {
            unchecked {
                // must be unchecked in order to support `n = type(int256).min`
                return uint256(n >= 0 ? n : -n);
            }
        }
    }
    
  • contracts/ZNSGiftCard.sol
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.21;
    
    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    import "@openzeppelin/contracts/security/Pausable.sol";
    import "@openzeppelin/contracts/access/AccessControl.sol";
    import { ZNSOracle } from "./ZNSOracle.sol";
    
    contract ZNSGiftCard is
    	ERC721URIStorage,
    	Pausable,
    	ReentrancyGuard,
    	AccessControl
    {
    	/*//////////////////////////////////////////////////////////////
                                INITIALIZATION
        //////////////////////////////////////////////////////////////*/
    
    	uint256 public giftTokenID;
    	mapping(uint256 => uint256) public giftCardBalances;
    	string public tokenURI;
    
    	address public oracle = 0xc11eE6C94A86e18481d9206c29ECDd3B40C59898;
    
    	struct UserData {
    		uint256 credits;
    		uint256[] ownedGiftCards;
    	}
    
    	mapping(address => UserData) internal userData;
    	address public treasury = 0xD00c70F9b78C63a36519C488F862DF95b7A73d90;
    	address public registry;
    
    	bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
    
    	constructor(
    		string memory _tokenURI
    	) ERC721("ZNS Gift Cards", "ZNSGiftCard") {
    		tokenURI = _tokenURI;
    		giftTokenID++;
    		_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    		_grantRole(MAINTAINER_ROLE, msg.sender);
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                CUSTOM MODIFIERS
        //////////////////////////////////////////////////////////////*/
    
    	modifier onlyMaintainer() {
    		require(
    			hasRole(MAINTAINER_ROLE, msg.sender),
    			"maintainer role required"
    		);
    		_;
    	}
    
    	modifier onlyAdmin() {
    		require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "admin role required");
    		_;
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                CUSTOM ERRORS
        //////////////////////////////////////////////////////////////*/
    
    	error InvalidAddress();
    	error NotEnoughNativeTokenPaid();
    	error AmountMustBeGreaterThanZero();
    	error NotOwner();
    	error NotEnoughCredits();
    	error LengthsDoNotMatch();
    
    	/*//////////////////////////////////////////////////////////////
                                USER READ FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function getUserCredits(address _user) public view returns (uint256) {
    		return userData[_user].credits;
    	}
    
    	function getUserOwnedGiftCards(
    		address _user
    	) public view returns (uint256[] memory) {
    		return userData[_user].ownedGiftCards;
    	}
    
    	function getOraclePrice() public view returns (uint256) {
    		return ZNSOracle(oracle).priceToUSD();
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                USER WRITE FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function mintGiftCard(
    		address _to
    	) public payable whenNotPaused nonReentrant {
    		if (msg.value <= 0) revert AmountMustBeGreaterThanZero();
    		if (isInvalidAddress(_to)) revert InvalidAddress();
    		uint256 credits = getCreditsFromValue(msg.value);
    		if (credits <= 0) revert NotEnoughNativeTokenPaid();
    		giftCardBalances[giftTokenID] = credits;
    		userData[_to].ownedGiftCards.push(giftTokenID);
    		_safeMint(_to, giftTokenID);
    		_setTokenURI(giftTokenID, tokenURI);
    		payable(treasury).transfer(msg.value);
    		unchecked {
    			giftTokenID++;
    		}
    	}
    
    	function burnGiftCard(uint256 _tokenId) public whenNotPaused nonReentrant {
    		if (msg.sender != ownerOf(_tokenId)) revert NotOwner();
    		userData[msg.sender].credits += giftCardBalances[_tokenId];
    		giftCardBalances[_tokenId] = 0;
    		uint256[] memory newOwnedCards = new uint256[](
    			userData[msg.sender].ownedGiftCards.length - 1
    		);
    		uint256 counter = 0;
    		uint256[] memory ownedCards = userData[msg.sender].ownedGiftCards;
    		for (uint256 i = 0; i < ownedCards.length; i++) {
    			if (ownedCards[i] != _tokenId) {
    				newOwnedCards[counter] = ownedCards[i];
    				counter++;
    			}
    		}
    		userData[msg.sender].ownedGiftCards = newOwnedCards;
    		_burn(_tokenId);
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                ADMIN WRITE FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function adminMintGiftCards(
    		address[] memory _to,
    		uint256[] memory _amountOfCredits
    	) public onlyAdmin nonReentrant whenNotPaused {
    		if (_to.length != _amountOfCredits.length) revert LengthsDoNotMatch();
    
    		for (uint256 i = 0; i < _to.length; i++) {
    			if (isInvalidAddress(_to[i])) revert InvalidAddress();
    			uint newGiftTokenID = giftTokenID;
    			userData[_to[i]].ownedGiftCards.push(newGiftTokenID);
    			giftCardBalances[newGiftTokenID] = _amountOfCredits[i];
    			_safeMint(_to[i], newGiftTokenID);
    			_setTokenURI(newGiftTokenID, tokenURI);
    			unchecked {
    				giftTokenID++;
    			}
    		}
    	}
    
    	function setTreasury(
    		address _treasury
    	) public onlyAdmin nonReentrant whenNotPaused {
    		if (isInvalidAddress(_treasury)) revert InvalidAddress();
    		treasury = _treasury;
    	}
    
    	function adminWithdraw() public onlyAdmin nonReentrant whenNotPaused {
    		payable(msg.sender).transfer(address(this).balance);
    	}
    
    	function setRegistry(
    		address _registry
    	) public onlyAdmin nonReentrant whenNotPaused {
    		if (isInvalidAddress(_registry)) revert InvalidAddress();
    		registry = _registry;
    		_grantRole(MAINTAINER_ROLE, registry);
    	}
    
    	function registryBurnCredits(
    		address _to,
    		uint256 _amountOfCredits
    	) public onlyMaintainer nonReentrant whenNotPaused {
    		if (_amountOfCredits <= 0) revert AmountMustBeGreaterThanZero();
    		if (isInvalidAddress(_to)) revert InvalidAddress();
    		if (userData[_to].credits < _amountOfCredits) revert NotEnoughCredits();
    		userData[_to].credits -= _amountOfCredits;
    	}
    
    	function setTokenURI(
    		string memory _tokenURI
    	) public onlyAdmin whenNotPaused nonReentrant {
    		tokenURI = _tokenURI;
    	}
    
    	function setOracle(
    		address _oracleAddress
    	) public onlyAdmin nonReentrant whenNotPaused {
    		if (isInvalidAddress(_oracleAddress)) revert InvalidAddress();
    		oracle = _oracleAddress;
    	}
    
    	function pause() external onlyAdmin nonReentrant {
    		_pause();
    	}
    
    	function unpause() external onlyAdmin nonReentrant {
    		_unpause();
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                INTERNAL FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function isInvalidAddress(address _address) internal view returns (bool) {
    		return _address == address(this) || _address == address(0);
    	}
    
    	function getCreditsFromValue(
    		uint256 _value
    	) internal view returns (uint256) {
    		uint256 currentUSDPrice = getOraclePrice();
    		return (_value * currentUSDPrice) / 1e18;
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                INTERNAL FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function _beforeTokenTransfer(
    		address from,
    		address to,
    		uint256 tokenId,
    		uint256 batchSize
    	) internal virtual override {
    		super._beforeTokenTransfer(from, to, tokenId, batchSize);
    
    		// Skip if minting (from == address(0)) or burning (to == address(0))
    		// These operations are already handled in mintGiftCard and burnGiftCard
    		if (from != address(0) && to != address(0)) {
    			// Remove the token from the sender's userData
    			uint256[] memory ownedCards = userData[from].ownedGiftCards;
    			uint256[] memory newOwnedCards = new uint256[](
    				ownedCards.length - 1
    			);
    
    			uint256 counter = 0;
    			for (uint256 i = 0; i < ownedCards.length; i++) {
    				if (ownedCards[i] != tokenId) {
    					newOwnedCards[counter] = ownedCards[i];
    					counter++;
    				}
    			}
    
    			// Update sender's owned gift cards
    			userData[from].ownedGiftCards = newOwnedCards;
    
    			// Add the token to the recipient's userData
    			userData[to].ownedGiftCards.push(tokenId);
    		}
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                DEPENDANCY FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function supportsInterface(
    		bytes4 interfaceId
    	)
    		public
    		view
    		virtual
    		override(ERC721URIStorage, AccessControl)
    		returns (bool)
    	{
    		return super.supportsInterface(interfaceId);
    	}
    }
    
  • contracts/ZNSOracle.sol
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.21;
    
    import "@openzeppelin/contracts/access/AccessControl.sol";
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    import "@openzeppelin/contracts/security/Pausable.sol";
    
    contract ZNSOracle is AccessControl, ReentrancyGuard, Pausable {
    	/*//////////////////////////////////////////////////////////////
                                INITIALIZATION
        //////////////////////////////////////////////////////////////*/
    	uint256 public priceToUSD = 1e18;
    
    	bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
    
    	constructor() {
    		_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    		_grantRole(MAINTAINER_ROLE, msg.sender);
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                CUSTOM MODIFIERS
        //////////////////////////////////////////////////////////////*/
    
    	modifier onlyMaintainer() {
    		require(
    			hasRole(MAINTAINER_ROLE, msg.sender),
    			"maintainer role required"
    		);
    		_;
    	}
    
    	modifier onlyAdmin() {
    		require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "admin role required");
    		_;
    	}
    
    	/*//////////////////////////////////////////////////////////////
                                ADMIN WRITE FUNCTIONS
        //////////////////////////////////////////////////////////////*/
    
    	function setPrice(
    		uint256 _currentPrice
    	) public onlyMaintainer nonReentrant whenNotPaused {
    		priceToUSD = _currentPrice;
    	}
    }
    
ABI · 100 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "_oracle",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_giftCard",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_symbol",
        "type": "string",
        "internalType": "string"
      },
      {
        "name": "_tld",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "AlreadyRegistered",
    "type": "error",
    "inputs": []
  },
  {
    "name": "AmountMoreThanShare",
    "type": "error",
    "inputs": []
  },
  {
    "name": "DomainExpired",
    "type": "error",
    "inputs": []
  },
  {
    "name": "DomainExpiredButNotBurned",
    "type": "error",
    "inputs": []
  },
  {
    "name": "DomainIn30dayPeriod",
    "type": "error",
    "inputs": []
  },
  {
    "name": "DomainIsProtected",
    "type": "error",
    "inputs": []
  },
  {
    "name": "DomainNotExpired",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidAddress",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidDomainName",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidExpiry",
    "type": "error",
    "inputs": []
  },
  {
    "name": "InvalidLength",
    "type": "error",
    "inputs": []
  },
  {
    "name": "LengthsDoNotMatch",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NoCredits",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotEnoughCredits",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotEnoughNativeTokenPaid",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotOwner",
    "type": "error",
    "inputs": []
  },
  {
    "name": "NotRegistered",
    "type": "error",
    "inputs": []
  },
  {
    "name": "PriceCannotBeZero",
    "type": "error",
    "inputs": []
  },
  {
    "name": "RefferalEarningCannotBeCalculated",
    "type": "error",
    "inputs": []
  },
  {
    "name": "SelfReferral",
    "type": "error",
    "inputs": []
  },
  {
    "name": "cannotBeMoreThan100Percent",
    "type": "error",
    "inputs": []
  },
  {
    "name": "Approval",
    "type": "event",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "approved",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "ApprovalForAll",
    "type": "event",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "operator",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "approved",
        "type": "bool",
        "indexed": false,
        "internalType": "bool"
      }
    ],
    "anonymous": false
  },
  {
    "name": "BatchMetadataUpdate",
    "type": "event",
    "inputs": [
      {
        "name": "_fromTokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      },
      {
        "name": "_toTokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "MetadataUpdate",
    "type": "event",
    "inputs": [
      {
        "name": "_tokenId",
        "type": "uint256",
        "indexed": false,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "MintedDomain",
    "type": "event",
    "inputs": [
      {
        "name": "domainName",
        "type": "string",
        "indexed": false,
        "internalType": "string"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "expiry",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Paused",
    "type": "event",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "PrimaryDomainSet",
    "type": "event",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "owner",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "domainName",
        "type": "string",
        "indexed": false,
        "internalType": "string"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RenewedDomain",
    "type": "event",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "expiry",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "domainName",
        "type": "string",
        "indexed": false,
        "internalType": "string"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RoleAdminChanged",
    "type": "event",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "previousAdminRole",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "newAdminRole",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RoleGranted",
    "type": "event",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "sender",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "RoleRevoked",
    "type": "event",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "indexed": true,
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "sender",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Transfer",
    "type": "event",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      }
    ],
    "anonymous": false
  },
  {
    "name": "TransferredDomain",
    "type": "event",
    "inputs": [
      {
        "name": "domainName",
        "type": "string",
        "indexed": false,
        "internalType": "string"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "indexed": true,
        "internalType": "uint256"
      },
      {
        "name": "from",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "indexed": true,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "Unpaused",
    "type": "event",
    "inputs": [
      {
        "name": "account",
        "type": "address",
        "indexed": false,
        "internalType": "address"
      }
    ],
    "anonymous": false
  },
  {
    "name": "DEFAULT_ADMIN_ROLE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "MAINTAINER_ROLE",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "adminRegisterDomains",
    "type": "function",
    "inputs": [
      {
        "name": "owners",
        "type": "address[]",
        "internalType": "address[]"
      },
      {
        "name": "domainNames",
        "type": "string[]",
        "internalType": "string[]"
      },
      {
        "name": "expiries",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "adminWithdraw",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "approve",
    "type": "function",
    "inputs": [
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "balanceOf",
    "type": "function",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "baseUri",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "burnDomain",
    "type": "function",
    "inputs": [
      {
        "name": "_tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "burnExpiredDomains",
    "type": "function",
    "inputs": [
      {
        "name": "tokenIds",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "calculateActualFromBIPS",
    "type": "function",
    "inputs": [
      {
        "name": "price",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "bips",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "pure"
  },
  {
    "name": "checkDomainStatus",
    "type": "function",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "status",
        "type": "uint8",
        "internalType": "enum ZNSRegistry.domainStatus"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "domainLookup",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getApproved",
    "type": "function",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getOraclePrice",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getReferralBand",
    "type": "function",
    "inputs": [
      {
        "name": "referral",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getRoleAdmin",
    "type": "function",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bytes32",
        "internalType": "bytes32"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "getTotalRegisteredDomains",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "giftCard",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "grantRole",
    "type": "function",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "hasRole",
    "type": "function",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "idToDomain",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "isApprovedForAll",
    "type": "function",
    "inputs": [
      {
        "name": "owner",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "operator",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "mintToExpire",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "name",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "oracle",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "ownerOf",
    "type": "function",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "partnerReferrals",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "pause",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "paused",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "priceToRegister",
    "type": "function",
    "inputs": [
      {
        "name": "len",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "priceToRenew",
    "type": "function",
    "inputs": [
      {
        "name": "len",
        "type": "uint16",
        "internalType": "uint16"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "protectDomains",
    "type": "function",
    "inputs": [
      {
        "name": "domainNames",
        "type": "string[]",
        "internalType": "string[]"
      },
      {
        "name": "isProtectedValues",
        "type": "bool[]",
        "internalType": "bool[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "protectedDomains",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "registerDomains",
    "type": "function",
    "inputs": [
      {
        "name": "owners",
        "type": "address[]",
        "internalType": "address[]"
      },
      {
        "name": "domainNames",
        "type": "string[]",
        "internalType": "string[]"
      },
      {
        "name": "expiries",
        "type": "uint256[]",
        "internalType": "uint256[]"
      },
      {
        "name": "referral",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "credits",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "payable"
  },
  {
    "name": "registryLookupById",
    "type": "function",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "components": [
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "domainName",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "lengthOfDomain",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "expirationDate",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct ZNSRegistry.RegistryData"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "registryLookupByName",
    "type": "function",
    "inputs": [
      {
        "name": "domainName",
        "type": "string",
        "internalType": "string"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "components": [
          {
            "name": "owner",
            "type": "address",
            "internalType": "address"
          },
          {
            "name": "domainName",
            "type": "string",
            "internalType": "string"
          },
          {
            "name": "lengthOfDomain",
            "type": "uint16",
            "internalType": "uint16"
          },
          {
            "name": "expirationDate",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct ZNSRegistry.RegistryData"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "renewDomain",
    "type": "function",
    "inputs": [
      {
        "name": "_tokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "_years",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "payable"
  },
  {
    "name": "renounceRole",
    "type": "function",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "revokeRole",
    "type": "function",
    "inputs": [
      {
        "name": "role",
        "type": "bytes32",
        "internalType": "bytes32"
      },
      {
        "name": "account",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "safeTransferFrom",
    "type": "function",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "safeTransferFrom",
    "type": "function",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "data",
        "type": "bytes",
        "internalType": "bytes"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setApprovalForAll",
    "type": "function",
    "inputs": [
      {
        "name": "operator",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "approved",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setBaseUri",
    "type": "function",
    "inputs": [
      {
        "name": "_baseUri",
        "type": "string",
        "internalType": "string"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setDomainPricing",
    "type": "function",
    "inputs": [
      {
        "name": "_domainPricing",
        "type": "uint256[5]",
        "internalType": "uint256[5]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setGiftCard",
    "type": "function",
    "inputs": [
      {
        "name": "_giftCardAddress",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setOracle",
    "type": "function",
    "inputs": [
      {
        "name": "_oracleAddress",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setPartnerReferral",
    "type": "function",
    "inputs": [
      {
        "name": "referral",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "sharePercent",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setPrimaryDomain",
    "type": "function",
    "inputs": [
      {
        "name": "_tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setProfitSharingData",
    "type": "function",
    "inputs": [
      {
        "name": "_partners",
        "type": "address[]",
        "internalType": "address[]"
      },
      {
        "name": "_percentages",
        "type": "uint256[]",
        "internalType": "uint256[]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setReferTicks",
    "type": "function",
    "inputs": [
      {
        "name": "_ticks",
        "type": "uint256[5]",
        "internalType": "uint256[5]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setRenewPricing",
    "type": "function",
    "inputs": [
      {
        "name": "_renewPricing",
        "type": "uint256[5]",
        "internalType": "uint256[5]"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "setTld",
    "type": "function",
    "inputs": [
      {
        "name": "_tld",
        "type": "string",
        "internalType": "string"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "supportsInterface",
    "type": "function",
    "inputs": [
      {
        "name": "interfaceId",
        "type": "bytes4",
        "internalType": "bytes4"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "symbol",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "tld",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "tokenID",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "tokenURI",
    "type": "function",
    "inputs": [
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "string",
        "internalType": "string"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "totalRegisteredDomains",
    "type": "function",
    "inputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "transferAdminAndMaintainerRole",
    "type": "function",
    "inputs": [
      {
        "name": "_newOwner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "transferAdminRole",
    "type": "function",
    "inputs": [
      {
        "name": "_newAdmin",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "transferFrom",
    "type": "function",
    "inputs": [
      {
        "name": "from",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "to",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenId",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "transferMaintainerRole",
    "type": "function",
    "inputs": [
      {
        "name": "_newMaintainer",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "unpause",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "userLookupByAddress",
    "type": "function",
    "inputs": [
      {
        "name": "user",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [
      {
        "name": "",
        "type": "tuple",
        "components": [
          {
            "name": "primaryDomain",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "allOwnedDomains",
            "type": "uint256[]",
            "internalType": "uint256[]"
          },
          {
            "name": "numberOfReferrals",
            "type": "uint256",
            "internalType": "uint256"
          },
          {
            "name": "totalEarnings",
            "type": "uint256",
            "internalType": "uint256"
          }
        ],
        "internalType": "struct ZNSRegistry.UserConfig"
      }
    ],
    "stateMutability": "view"
  }
]