// File: @ensdomains/ens-contracts/contracts/ethregistrar/IBaseRegistrar.sol



import "../registry/ENS.sol";
import "./IBaseRegistrar.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

interface IBaseRegistrar is IERC721 {
    event ControllerAdded(address indexed controller);
    event ControllerRemoved(address indexed controller);
    event NameMigrated(
        uint256 indexed id,
        address indexed owner,
        uint256 expires
    );
    event NameRegistered(
        uint256 indexed id,
        address indexed owner,
        uint256 expires
    );
    event NameRenewed(uint256 indexed id, uint256 expires);

    // Authorises a controller, who can register and renew domains.
    function addController(address controller) external;

    // Revoke controller permission for an address.
    function removeController(address controller) external;

    // Set the resolver for the TLD this registrar manages.
    function setResolver(address resolver) external;

    // Returns the expiration timestamp of the specified label hash.
    function nameExpires(uint256 id) external view returns (uint256);

    // Returns true if the specified name is available for registration.
    function available(uint256 id) external view returns (bool);

    /**
     * @dev Register a name.
     */
    function register(
        uint256 id,
        address owner,
        uint256 duration
    ) external returns (uint256);

    function renew(uint256 id, uint256 duration) external returns (uint256);

    /**
     * @dev Reclaim ownership of a name in ENS, if you own it in the registrar.
     */
    function reclaim(uint256 id, address owner) external;
}

// File: @ensdomains/ens-contracts/contracts/registry/ENS.sol



pragma solidity >=0.8.4;

interface ENS {
    // Logged when the owner of a node assigns a new owner to a subnode.
    event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);

    // Logged when the owner of a node transfers ownership to a new account.
    event Transfer(bytes32 indexed node, address owner);

    // Logged when the resolver for a node changes.
    event NewResolver(bytes32 indexed node, address resolver);

    // Logged when the TTL of a node changes
    event NewTTL(bytes32 indexed node, uint64 ttl);

    // Logged when an operator is added or removed.
    event ApprovalForAll(
        address indexed owner,
        address indexed operator,
        bool approved
    );

    function setRecord(
        bytes32 node,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeRecord(
        bytes32 node,
        bytes32 label,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeOwner(
        bytes32 node,
        bytes32 label,
        address owner
    ) external returns (bytes32);

    function setResolver(bytes32 node, address resolver) external;

    function setOwner(bytes32 node, address owner) external;

    function setTTL(bytes32 node, uint64 ttl) external;

    function setApprovalForAll(address operator, bool approved) external;

    function owner(bytes32 node) external view returns (address);

    function resolver(bytes32 node) external view returns (address);

    function ttl(bytes32 node) external view returns (uint64);

    function recordExists(bytes32 node) external view returns (bool);

    function isApprovedForAll(
        address owner,
        address operator
    ) external view returns (bool);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/ResolverBase.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "./profiles/IVersionableResolver.sol";

abstract contract ResolverBase is ERC165, IVersionableResolver {
    mapping(bytes32 => uint64) public recordVersions;

    function isAuthorised(bytes32 node) internal view virtual returns (bool);

    modifier authorised(bytes32 node) {
        require(isAuthorised(node));
        _;
    }

    /**
     * Increments the record version associated with an ENS node.
     * May only be called by the owner of that node in the ENS registry.
     * @param node The node to update.
     */
    function clearRecords(bytes32 node) public virtual authorised(node) {
        recordVersions[node]++;
        emit VersionChanged(node, recordVersions[node]);
    }

    function supportsInterface(
        bytes4 interfaceID
    ) public view virtual override returns (bool) {
        return
            interfaceID == type(IVersionableResolver).interfaceId ||
            super.supportsInterface(interfaceID);
    }
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IABIResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IABIResolver {
    event ABIChanged(bytes32 indexed node, uint256 indexed contentType);

    /**
     * Returns the ABI associated with an ENS node.
     * Defined in EIP205.
     * @param node The ENS node to query
     * @param contentTypes A bitwise OR of the ABI formats accepted by the caller.
     * @return contentType The content type of the return value
     * @return data The ABI data
     */
    function ABI(
        bytes32 node,
        uint256 contentTypes
    ) external view returns (uint256, bytes memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IAddrResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

/**
 * Interface for the legacy (ETH-only) addr function.
 */
interface IAddrResolver {
    event AddrChanged(bytes32 indexed node, address a);

    /**
     * Returns the address associated with an ENS node.
     * @param node The ENS node to query.
     * @return The associated address.
     */
    function addr(bytes32 node) external view returns (address payable);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IAddressResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

/**
 * Interface for the new (multicoin) addr function.
 */
interface IAddressResolver {
    event AddressChanged(
        bytes32 indexed node,
        uint256 coinType,
        bytes newAddress
    );

    function addr(
        bytes32 node,
        uint256 coinType
    ) external view returns (bytes memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IContentHashResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IContentHashResolver {
    event ContenthashChanged(bytes32 indexed node, bytes hash);

    /**
     * Returns the contenthash associated with an ENS node.
     * @param node The ENS node to query.
     * @return The associated contenthash.
     */
    function contenthash(bytes32 node) external view returns (bytes memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IDNSRecordResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IDNSRecordResolver {
    // DNSRecordChanged is emitted whenever a given node/name/resource's RRSET is updated.
    event DNSRecordChanged(
        bytes32 indexed node,
        bytes name,
        uint16 resource,
        bytes record
    );
    // DNSRecordDeleted is emitted whenever a given node/name/resource's RRSET is deleted.
    event DNSRecordDeleted(bytes32 indexed node, bytes name, uint16 resource);

    /**
     * Obtain a DNS record.
     * @param node the namehash of the node for which to fetch the record
     * @param name the keccak-256 hash of the fully-qualified name for which to fetch the record
     * @param resource the ID of the resource as per https://en.wikipedia.org/wiki/List_of_DNS_record_types
     * @return the DNS record in wire format if present, otherwise empty
     */
    function dnsRecord(
        bytes32 node,
        bytes32 name,
        uint16 resource
    ) external view returns (bytes memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IDNSZoneResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IDNSZoneResolver {
    // DNSZonehashChanged is emitted whenever a given node's zone hash is updated.
    event DNSZonehashChanged(
        bytes32 indexed node,
        bytes lastzonehash,
        bytes zonehash
    );

    /**
     * zonehash obtains the hash for the zone.
     * @param node The ENS node to query.
     * @return The associated contenthash.
     */
    function zonehash(bytes32 node) external view returns (bytes memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IExtendedResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

interface IExtendedResolver {
    function resolve(
        bytes memory name,
        bytes memory data
    ) external view returns (bytes memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IInterfaceResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IInterfaceResolver {
    event InterfaceChanged(
        bytes32 indexed node,
        bytes4 indexed interfaceID,
        address implementer
    );

    /**
     * Returns the address of a contract that implements the specified interface for this name.
     * If an implementer has not been set for this interfaceID and name, the resolver will query
     * the contract at `addr()`. If `addr()` is set, a contract exists at that address, and that
     * contract implements EIP165 and returns `true` for the specified interfaceID, its address
     * will be returned.
     * @param node The ENS node to query.
     * @param interfaceID The EIP 165 interface ID to check for.
     * @return The address that implements this interface, or 0 if the interface is unsupported.
     */
    function interfaceImplementer(
        bytes32 node,
        bytes4 interfaceID
    ) external view returns (address);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/INameResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface INameResolver {
    event NameChanged(bytes32 indexed node, string name);

    /**
     * Returns the name associated with an ENS node, for reverse records.
     * Defined in EIP181.
     * @param node The ENS node to query.
     * @return The associated name.
     */
    function name(bytes32 node) external view returns (string memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IPubkeyResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IPubkeyResolver {
    event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y);

    /**
     * Returns the SECP256k1 public key associated with an ENS node.
     * Defined in EIP 619.
     * @param node The ENS node to query
     * @return x The X coordinate of the curve point for the public key.
     * @return y The Y coordinate of the curve point for the public key.
     */
    function pubkey(bytes32 node) external view returns (bytes32 x, bytes32 y);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface ITextResolver {
    event TextChanged(
        bytes32 indexed node,
        string indexed indexedKey,
        string key,
        string value
    );

    /**
     * Returns the text data associated with an ENS node and key.
     * @param node The ENS node to query.
     * @param key The text data key to query.
     * @return The associated text data.
     */
    function text(
        bytes32 node,
        string calldata key
    ) external view returns (string memory);
}

// File: @ensdomains/ens-contracts/contracts/resolvers/profiles/IVersionableResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;

interface IVersionableResolver {
    event VersionChanged(bytes32 indexed node, uint64 newVersion);

    function recordVersions(bytes32 node) external view returns (uint64);
}

// File: @ensdomains/ens-contracts/contracts/reverseRegistrar/IReverseRegistrar.sol



pragma solidity >=0.8.4;

interface IReverseRegistrar {
    function setDefaultResolver(address resolver) external;

    function claim(address owner) external returns (bytes32);

    function claimForAddr(
        address addr,
        address owner,
        address resolver
    ) external returns (bytes32);

    function claimWithResolver(
        address owner,
        address resolver
    ) external returns (bytes32);

    function setName(string memory name) external returns (bytes32);

    function setNameForAddr(
        address addr,
        address owner,
        address resolver,
        string memory name
    ) external returns (bytes32);

    function node(address addr) external pure returns (bytes32);
}

// File: @ensdomains/ens-contracts/contracts/reverseRegistrar/ReverseClaimer.sol



//SPDX-License-Identifier: MIT
pragma solidity >=0.8.17 <0.9.0;

import {ENS} from "../registry/ENS.sol";
import {IReverseRegistrar} from "../reverseRegistrar/IReverseRegistrar.sol";

contract ReverseClaimer {
    bytes32 constant ADDR_REVERSE_NODE =
        0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;

    constructor(ENS ens, address claimant) {
        IReverseRegistrar reverseRegistrar = IReverseRegistrar(
            ens.owner(ADDR_REVERSE_NODE)
        );
        reverseRegistrar.claim(claimant);
    }
}

// File: @ensdomains/ens-contracts/contracts/utils/NameEncoder.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {BytesUtils} from "../wrapper/BytesUtils.sol";

library NameEncoder {
    using BytesUtils for bytes;

    function dnsEncodeName(
        string memory name
    ) internal pure returns (bytes memory dnsName, bytes32 node) {
        uint8 labelLength = 0;
        bytes memory bytesName = bytes(name);
        uint256 length = bytesName.length;
        dnsName = new bytes(length + 2);
        node = 0;
        if (length == 0) {
            dnsName[0] = 0;
            return (dnsName, node);
        }

        // use unchecked to save gas since we check for an underflow
        // and we check for the length before the loop
        unchecked {
            for (uint256 i = length - 1; i >= 0; i--) {
                if (bytesName[i] == ".") {
                    dnsName[i + 1] = bytes1(labelLength);
                    node = keccak256(
                        abi.encodePacked(
                            node,
                            bytesName.keccak(i + 1, labelLength)
                        )
                    );
                    labelLength = 0;
                } else {
                    labelLength += 1;
                    dnsName[i + 1] = bytesName[i];
                }
                if (i == 0) {
                    break;
                }
            }
        }

        node = keccak256(
            abi.encodePacked(node, bytesName.keccak(0, labelLength))
        );

        dnsName[0] = bytes1(labelLength);
        return (dnsName, node);
    }
}

// File: @ensdomains/ens-contracts/contracts/wrapper/BytesUtils.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.17;

library BytesUtils {
    /*
     * @dev Returns the keccak-256 hash of a byte range.
     * @param self The byte string to hash.
     * @param offset The position to start hashing at.
     * @param len The number of bytes to hash.
     * @return The hash of the byte range.
     */
    function keccak(
        bytes memory self,
        uint256 offset,
        uint256 len
    ) internal pure returns (bytes32 ret) {
        require(offset + len <= self.length);
        assembly {
            ret := keccak256(add(add(self, 32), offset), len)
        }
    }

    /**
     * @dev Returns the ENS namehash of a DNS-encoded name.
     * @param self The DNS-encoded name to hash.
     * @param offset The offset at which to start hashing.
     * @return The namehash of the name.
     */
    function namehash(
        bytes memory self,
        uint256 offset
    ) internal pure returns (bytes32) {
        (bytes32 labelhash, uint256 newOffset) = readLabel(self, offset);
        if (labelhash == bytes32(0)) {
            require(offset == self.length - 1, "namehash: Junk at end of name");
            return bytes32(0);
        }
        return
            keccak256(abi.encodePacked(namehash(self, newOffset), labelhash));
    }

    /**
     * @dev Returns the keccak-256 hash of a DNS-encoded label, and the offset to the start of the next label.
     * @param self The byte string to read a label from.
     * @param idx The index to read a label at.
     * @return labelhash The hash of the label at the specified index, or 0 if it is the last label.
     * @return newIdx The index of the start of the next label.
     */
    function readLabel(
        bytes memory self,
        uint256 idx
    ) internal pure returns (bytes32 labelhash, uint256 newIdx) {
        require(idx < self.length, "readLabel: Index out of bounds");
        uint256 len = uint256(uint8(self[idx]));
        if (len > 0) {
            labelhash = keccak(self, idx + 1, len);
        } else {
            labelhash = bytes32(0);
        }
        newIdx = idx + len + 1;
    }
}

// File: @ensdomains/ens-contracts/contracts/wrapper/IMetadataService.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.17;

interface IMetadataService {
    function uri(uint256) external view returns (string memory);
}

// File: @ensdomains/ens-contracts/contracts/wrapper/INameWrapper.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.17;

import "../registry/ENS.sol";
import "../ethregistrar/IBaseRegistrar.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "./IMetadataService.sol";
import "./INameWrapperUpgrade.sol";

uint32 constant CANNOT_UNWRAP = 1;
uint32 constant CANNOT_BURN_FUSES = 2;
uint32 constant CANNOT_TRANSFER = 4;
uint32 constant CANNOT_SET_RESOLVER = 8;
uint32 constant CANNOT_SET_TTL = 16;
uint32 constant CANNOT_CREATE_SUBDOMAIN = 32;
uint32 constant CANNOT_APPROVE = 64;
//uint16 reserved for parent controlled fuses from bit 17 to bit 32
uint32 constant PARENT_CANNOT_CONTROL = 1 << 16;
uint32 constant IS_DOT_ETH = 1 << 17;
uint32 constant CAN_EXTEND_EXPIRY = 1 << 18;
uint32 constant CAN_DO_EVERYTHING = 0;
uint32 constant PARENT_CONTROLLED_FUSES = 0xFFFF0000;
// all fuses apart from IS_DOT_ETH
uint32 constant USER_SETTABLE_FUSES = 0xFFFDFFFF;

interface INameWrapper is IERC1155 {
    event NameWrapped(
        bytes32 indexed node,
        bytes name,
        address owner,
        uint32 fuses,
        uint64 expiry
    );

    event NameUnwrapped(bytes32 indexed node, address owner);

    event FusesSet(bytes32 indexed node, uint32 fuses);
    event ExpiryExtended(bytes32 indexed node, uint64 expiry);

    function ens() external view returns (ENS);

    function registrar() external view returns (IBaseRegistrar);

    function metadataService() external view returns (IMetadataService);

    function names(bytes32) external view returns (bytes memory);

    function name() external view returns (string memory);

    function upgradeContract() external view returns (INameWrapperUpgrade);

    function supportsInterface(bytes4 interfaceID) external view returns (bool);

    function wrap(
        bytes calldata name,
        address wrappedOwner,
        address resolver
    ) external;

    function wrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint16 ownerControlledFuses,
        address resolver
    ) external returns (uint64 expires);

    function registerAndWrapETH2LD(
        string calldata label,
        address wrappedOwner,
        uint256 duration,
        address resolver,
        uint16 ownerControlledFuses
    ) external returns (uint256 registrarExpiry);

    function renew(
        uint256 labelHash,
        uint256 duration
    ) external returns (uint256 expires);

    function unwrap(bytes32 node, bytes32 label, address owner) external;

    function unwrapETH2LD(
        bytes32 label,
        address newRegistrant,
        address newController
    ) external;

    function upgrade(bytes calldata name, bytes calldata extraData) external;

    function setFuses(
        bytes32 node,
        uint16 ownerControlledFuses
    ) external returns (uint32 newFuses);

    function setChildFuses(
        bytes32 parentNode,
        bytes32 labelhash,
        uint32 fuses,
        uint64 expiry
    ) external;

    function setSubnodeRecord(
        bytes32 node,
        string calldata label,
        address owner,
        address resolver,
        uint64 ttl,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function setRecord(
        bytes32 node,
        address owner,
        address resolver,
        uint64 ttl
    ) external;

    function setSubnodeOwner(
        bytes32 node,
        string calldata label,
        address newOwner,
        uint32 fuses,
        uint64 expiry
    ) external returns (bytes32);

    function extendExpiry(
        bytes32 node,
        bytes32 labelhash,
        uint64 expiry
    ) external returns (uint64);

    function canModifyName(
        bytes32 node,
        address addr
    ) external view returns (bool);

    function setResolver(bytes32 node, address resolver) external;

    function setTTL(bytes32 node, uint64 ttl) external;

    function ownerOf(uint256 id) external view returns (address owner);

    function approve(address to, uint256 tokenId) external;

    function getApproved(uint256 tokenId) external view returns (address);

    function getData(
        uint256 id
    ) external view returns (address, uint32, uint64);

    function setMetadataService(IMetadataService _metadataService) external;

    function uri(uint256 tokenId) external view returns (string memory);

    function setUpgradeContract(INameWrapperUpgrade _upgradeAddress) external;

    function allFusesBurned(
        bytes32 node,
        uint32 fuseMask
    ) external view returns (bool);

    function isWrapped(bytes32) external view returns (bool);

    function isWrapped(bytes32, bytes32) external view returns (bool);
}

// File: @ensdomains/ens-contracts/contracts/wrapper/INameWrapperUpgrade.sol



//SPDX-License-Identifier: MIT
pragma solidity ~0.8.17;

interface INameWrapperUpgrade {
    function wrapFromUpgrade(
        bytes calldata name,
        address wrappedOwner,
        uint32 fuses,
        uint64 expiry,
        address approved,
        bytes calldata extraData
    ) external;
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

// File: @openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}

// File: @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);
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
    }
}

// File: @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;
    }
}

// File: @openzeppelin/contracts/utils/introspection/ERC165Checker.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC165Checker.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface.
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
            !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     *
     * _Available since v3.4._
     */
    function getSupportedInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool[] memory) {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     *
     * Some precompiled contracts will falsely indicate support for a given interface, so caution
     * should be exercised when using this function.
     *
     * Interface identification is specified in ERC-165.
     */
    function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

// File: @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);
}

// File: contracts/ensGuilds/GuildsResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import { NameEncoder } from "@ensdomains/ens-contracts/contracts/utils/NameEncoder.sol";
import { ReverseClaimer } from "@ensdomains/ens-contracts/contracts/reverseRegistrar/ReverseClaimer.sol";
import { ENS } from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import { INameWrapper } from "@ensdomains/ens-contracts/contracts/wrapper/INameWrapper.sol";

import { WildcardResolverBase } from "../ensWildcardResolvers/WildcardResolverBase.sol";
import { IENSGuilds } from "./interfaces/IENSGuilds.sol";

contract GuildsResolver is WildcardResolverBase, ReverseClaimer {
    using NameEncoder for string;

    IENSGuilds public ensGuilds;

    // guildEnsNode => recordVersion => keccak256(tag) => tagOwner
    mapping(bytes32 => mapping(uint256 => mapping(bytes32 => address))) private _guildRecords;

    // used to clear all of a Guild's ENS records
    mapping(bytes32 => uint256) private _guildRecordVersions;

    modifier onlyEnsGuildsContract() {
        // solhint-disable-next-line reason-string, custom-errors
        require(_msgSender() == address(ensGuilds));
        _;
    }

    constructor(
        ENS _ensRegistry,
        INameWrapper _ensNameWrapper,
        address reverseRecordOwner
    ) WildcardResolverBase(_ensRegistry, _ensNameWrapper) ReverseClaimer(_ensRegistry, reverseRecordOwner) {
        return;
    }

    function initialize(IENSGuilds _ensGuilds) external {
        // solhint-disable reason-string, custom-errors
        require(address(ensGuilds) == address(0));
        require(_ensGuilds.supportsInterface(type(IENSGuilds).interfaceId));
        // solhint-enable reason-string, custom-errors

        ensGuilds = _ensGuilds;
    }

    function onGuildRegistered(string calldata guildName) external onlyEnsGuildsContract {
        // need to keep track of the mapping from the DNS-encoded version
        // of the guild name to its namehash-encoded version
        (bytes memory dnsEncodedName, bytes32 ensNode) = guildName.dnsEncodeName();
        parentEnsNodes[dnsEncodedName] = ensNode;
    }

    /**
     * Sets the address associated with a guild tag.
     * May only be called by descendants of this contract
     */
    function setEnsForwardRecord(
        bytes32 guildEnsNode,
        string memory tag,
        address _addr
    ) external onlyEnsGuildsContract {
        uint256 version = _guildRecordVersions[guildEnsNode];
        bytes32 tagHash = keccak256(bytes(tag));
        _guildRecords[guildEnsNode][version][tagHash] = _addr;
    }

    function clearEnsRecordsForGuild(bytes32 guildEnsNode) external onlyEnsGuildsContract {
        _guildRecordVersions[guildEnsNode]++;
    }

    function setPassthroughTarget(bytes32 guildEnsNode, address resolver) external onlyEnsGuildsContract {
        _setPassthroughTarget(guildEnsNode, resolver);
    }

    function getTagOwner(bytes32 guildEnsNode, bytes32 tagHash) public view returns (address) {
        uint256 version = _guildRecordVersions[guildEnsNode];
        return _guildRecords[guildEnsNode][version][tagHash];
    }

    function _resolveWildcardEthAddr(
        bytes calldata childUtf8Encoded,
        bytes calldata parentDnsEncoded
    ) internal view override returns (address) {
        bytes32 guildEnsNode = parentEnsNodes[parentDnsEncoded];
        bytes32 tagHash = keccak256(childUtf8Encoded);
        return getTagOwner(guildEnsNode, tagHash);
    }

    function _resolveWildcardTextRecord(
        bytes calldata,
        bytes calldata,
        string calldata
    ) internal pure override returns (string memory) {
        // ENSGuilds doesn't set text records for Guild tags
        return "";
    }

    function isAuthorised(bytes32 node) internal view override returns (bool) {
        return _msgSender() == _nodeOwner(node);
    }
}

// File: contracts/ensGuilds/interfaces/IENSGuilds.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import { IERC1155MetadataURI } from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";

interface IENSGuilds is IERC1155MetadataURI {
    /** Events */
    event Registered(bytes32 indexed guildEnsNode);
    event Deregistered(bytes32 indexed guildEnsNode);
    event TagClaimed(bytes32 indexed guildEnsNode, bytes32 indexed tagHash, address recipient);
    event TagTransferred(bytes32 indexed guildEnsNode, bytes32 indexed tagHash, address from, address to);
    event TagRevoked(bytes32 indexed guildEnsNode, bytes32 indexed tagHash);
    event FeePolicyUpdated(bytes32 indexed guildEnsNode, address feePolicy);
    event TagsAuthPolicyUpdated(bytes32 indexed guildEnsNode, address tagsAuthPolicy);
    event AdminTransferred(bytes32 indexed guildEnsNode, address newAdmin);
    event SetActive(bytes32 indexed guildEnsNode, bool active);
    event TokenUriSet(bytes32 indexed guildEnsNode, string uri);

    /* Functions */

    /**
     * @notice Registers a new guild from an existing ENS domain.
     * Caller must be the ENS node's owner and ENSGuilds must have been designated an "operator" for the caller.
     * @param ensName The guild's full ENS name (e.g. 'my-guild.eth')
     * @param guildAdmin The address that will administrate this guild
     * @param feePolicy The address of an implementation of FeePolicy to use for minting new tags within this guild
     * @param tagsAuthPolicy The address of an implementation of TagsAuthPolicy to use for minting new tags
     * within this guild
     */
    function registerGuild(
        string calldata ensName,
        address guildAdmin,
        address feePolicy,
        address tagsAuthPolicy
    ) external;

    /**
     * @notice De-registers a registered guild.
     * Designates guild as inactive and marks all tags previously minted for that guild as eligible for revocation.
     * @param guildEnsNode The ENS namehash of the guild's domain
     */
    function deregisterGuild(bytes32 guildEnsNode) external;

    /**
     * @notice Claims a guild tag
     * @param guildEnsNode The namehash of the guild for which the tag should be claimed (e.g. namehash('my-guild.eth'))
     * @param tag The tag name to claim (e.g. 'foo' for foo.my-guild.eth). Assumes `tag` is already normalized per
     * ENS Name Processing rules
     * @param recipient The address that will receive this guild tag (usually same as the caller)
     * @param extraClaimArgs [Optional] Any additional arguments necessary for guild-specific logic,
     *  such as authorization
     */
    function claimGuildTag(
        bytes32 guildEnsNode,
        string calldata tag,
        address recipient,
        bytes calldata extraClaimArgs
    ) external payable;

    /**
     * @notice Transfers an existing guild tag
     * @param guildEnsNode The namehash of the guild for which the tag should be transferred
     * (e.g. namehash('my-guild.eth'))
     * @param tag The tag name to transfer (e.g. 'foo' for foo.my-guild.eth). Assumes `tag` is already normalized per
     * ENS Name Processing rules
     * @param recipient The address that will receive this guild tag
     * @param extraTransferArgs [Optional] Any additional arguments necessary for guild-specific logic,
     *  such as authorization
     */
    function transferGuildTag(
        bytes32 guildEnsNode,
        string calldata tag,
        address recipient,
        bytes calldata extraTransferArgs
    ) external;

    /**
     * @notice Claims multiple tags for a guild at once
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param tags Tags to be claimed
     * @param recipients Recipients of each tag to be claimed
     * @param extraClaimArgs Per-tag extra arguments required for guild-specific logic, such as authorization.
     * Must have same length as array of tagHashes, even if each array element is itself empty bytes
     */
    function claimGuildTagsBatch(
        bytes32 guildEnsNode,
        string[] calldata tags,
        address[] calldata recipients,
        bytes[] calldata extraClaimArgs
    ) external payable;

    /**
     * @notice Returns the current owner of the given guild tag.
     * Returns address(0) if no such guild or tag exists, or if the guild has been deregistered.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param tagHash The ENS namehash of the tag (e.g. keccak256('foo') for foo.my-guild.eth)
     */
    function tagOwner(bytes32 guildEnsNode, bytes32 tagHash) external view returns (address);

    /**
     * @notice Attempts to revoke an existing guild tag, if authorized by the guild's AuthPolicy.
     * Deregistered guilds will bypass auth checks for revocation of all tags.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param tag The tag to revoke
     * @param extraData [Optional] Any additional arguments necessary for assessing whether a tag may be revoked
     */
    function revokeGuildTag(bytes32 guildEnsNode, string calldata tag, bytes calldata extraData) external;

    /**
     * @notice Attempts to revoke multiple guild tags
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param tags tags to revoke
     * @param extraData Additional arguments necessary for assessing whether a tag may be revoked
     */
    function revokeGuildTagsBatch(bytes32 guildEnsNode, string[] calldata tags, bytes[] calldata extraData) external;

    /**
     * @notice Updates the FeePolicy for an existing guild. May only be called by the guild's registered admin.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param feePolicy The address of an implementation of FeePolicy to use for minting new tags within this guild
     */
    function updateGuildFeePolicy(bytes32 guildEnsNode, address feePolicy) external;

    /**
     * @notice Updates the TagsAuthPolicy for an existing guild. May only be called by the guild's registered admin.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param tagsAuthPolicy The address of an implementation of TagsAuthPolicy to use for
     * minting new tags within this guild
     */
    function updateGuildTagsAuthPolicy(bytes32 guildEnsNode, address tagsAuthPolicy) external;

    /**
     * @notice Sets the metadata URI string for fetching metadata for a guild's tag NFTs.
     * May only be called by the guild's registered admin.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param uri The ERC1155 metadata URL template
     */
    function setGuildTokenUri(bytes32 guildEnsNode, string calldata uri) external;

    /**
     * @notice Sets a guild as active or inactive. May only be called by the guild's registered admin.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param active The new status
     */
    function setGuildActive(bytes32 guildEnsNode, bool active) external;

    /**
     * @notice Returns the current admin registered for the given guild.
     * @param guildEnsNode The ENS namehash of the guild's domain
     */
    function guildAdmin(bytes32 guildEnsNode) external view returns (address);

    /**
     * @notice Transfers the role of guild admin to the given address.
     * May only be called by the guild's registered admin.
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param newAdmin The new admin
     */
    function transferGuildAdmin(bytes32 guildEnsNode, address newAdmin) external;

    /**
     * @notice Registers a resolver for the guild's root ENS name that will
     * answer queries about the parent name itself, or any child names that are
     * not Guild tags
     * @param guildEnsNode The ENS namehash of the guild's domain
     * @param fallbackResolver The fallback resolver
     */
    function setFallbackResolver(bytes32 guildEnsNode, address fallbackResolver) external;
}

// File: contracts/ensWildcardResolvers/IPublicResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import { IABIResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IABIResolver.sol";
import { IAddrResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IAddrResolver.sol";
import { IAddressResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IAddressResolver.sol";
import { IContentHashResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IContentHashResolver.sol";
import { IDNSRecordResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IDNSRecordResolver.sol";
import { IDNSZoneResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IDNSZoneResolver.sol";
import { IInterfaceResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IInterfaceResolver.sol";
import { INameResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/INameResolver.sol";
import { IPubkeyResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IPubkeyResolver.sol";
import { ITextResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol";

interface IPublicResolver is
    IABIResolver,
    IAddrResolver,
    IAddressResolver,
    IContentHashResolver,
    IDNSRecordResolver,
    IDNSZoneResolver,
    IInterfaceResolver,
    INameResolver,
    IPubkeyResolver,
    ITextResolver
{
    function setABI(bytes32 node, uint256 contentType, bytes calldata data) external;

    function setAddr(bytes32 node, address a) external;

    function setAddr(bytes32 node, uint256 coinType, bytes memory a) external;

    function setContenthash(bytes32 node, bytes calldata hash) external;

    function setDNSRecords(bytes32 node, bytes calldata data) external;

    function setZonehash(bytes32 node, bytes calldata hash) external;

    function setInterface(bytes32 node, bytes4 interfaceID, address implementer) external;

    function setName(bytes32 node, string calldata newName) external;

    function setPubkey(bytes32 node, bytes32 x, bytes32 y) external;

    function setText(bytes32 node, string calldata key, string calldata value) external;
}

// File: contracts/ensWildcardResolvers/PassthroughResolver.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import { ResolverBase } from "@ensdomains/ens-contracts/contracts/resolvers/ResolverBase.sol";
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import {
    IPublicResolver,
    IABIResolver,
    IAddrResolver,
    IAddressResolver,
    IContentHashResolver,
    IDNSRecordResolver,
    IDNSZoneResolver,
    IInterfaceResolver,
    INameResolver,
    IPubkeyResolver,
    ITextResolver
} from "./IPublicResolver.sol";

/**
 * @dev PassthroughResolver is an ENS Resolver that forwards all calls to a
 * fallback Resolver. A custom resolver may inherit this contract
 * to selectively implement specific record types, deferring all others to the
 * fallback Resolver (usually whatever public Resolver the ENS app set on behalf
 * of the user when a name was registered).
 *
 * The owner of the ENS name must first configure their PublicResolver to approve
 * this contract as an authorized manager on the owner's behalf for the setter
 * methods of PassthroughResolver to work. Note that this delegation is separate
 * from approvals set with the ENS Registry. ENS's public Registry and its public
 * Resolvers each have their own, independent concepts of approved managers.
 */
abstract contract PassthroughResolver is IPublicResolver, ResolverBase {
    using ERC165Checker for address;

    mapping(bytes32 => address) private _passthroughTargets;

    function isAuthorised(bytes32) internal view virtual override returns (bool);

    function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) {
        return
            interfaceID == type(IABIResolver).interfaceId ||
            interfaceID == type(IAddrResolver).interfaceId ||
            interfaceID == type(IAddressResolver).interfaceId ||
            interfaceID == type(IContentHashResolver).interfaceId ||
            interfaceID == type(IDNSRecordResolver).interfaceId ||
            interfaceID == type(IDNSZoneResolver).interfaceId ||
            interfaceID == type(IInterfaceResolver).interfaceId ||
            interfaceID == type(INameResolver).interfaceId ||
            interfaceID == type(IPubkeyResolver).interfaceId ||
            interfaceID == type(ITextResolver).interfaceId ||
            super.supportsInterface(interfaceID);
    }

    function getPassthroughTarget(bytes32 node) public view virtual returns (address resolver) {
        return _passthroughTargets[node];
    }

    function _setPassthroughTarget(bytes32 node, address target) internal {
        _passthroughTargets[node] = target;
    }

    function setABI(bytes32 node, uint256 contentType, bytes calldata data) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setABI(node, contentType, data);
    }

    // solhint-disable-next-line func-name-mixedcase
    function ABI(
        bytes32 node,
        uint256 contentTypes
    ) external view virtual override returns (uint256 a, bytes memory b) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IABIResolver).interfaceId)) {
            return IABIResolver(target).ABI(node, contentTypes);
        }
    }

    function setAddr(bytes32 node, address a) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setAddr(node, a);
    }

    function addr(bytes32 node) public view virtual override returns (address payable result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IAddrResolver).interfaceId)) {
            return IAddrResolver(target).addr(node);
        }
    }

    function setAddr(bytes32 node, uint256 coinType, bytes memory a) public virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setAddr(node, coinType, a);
    }

    function addr(bytes32 node, uint256 coinType) public view virtual override returns (bytes memory result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IAddressResolver).interfaceId)) {
            return IAddressResolver(target).addr(node, coinType);
        }
    }

    function setContenthash(bytes32 node, bytes calldata hash) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setContenthash(node, hash);
    }

    function contenthash(bytes32 node) external view virtual override returns (bytes memory result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IContentHashResolver).interfaceId)) {
            return IContentHashResolver(target).contenthash(node);
        }
    }

    function setDNSRecords(bytes32 node, bytes calldata data) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setDNSRecords(node, data);
    }

    function dnsRecord(
        bytes32 node,
        bytes32 name, // solhint-disable-line
        uint16 resource
    ) public view virtual override returns (bytes memory result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IDNSRecordResolver).interfaceId)) {
            return IDNSRecordResolver(target).dnsRecord(node, name, resource);
        }
    }

    function setZonehash(bytes32 node, bytes calldata hash) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setZonehash(node, hash);
    }

    function zonehash(bytes32 node) external view virtual override returns (bytes memory result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IDNSZoneResolver).interfaceId)) {
            return IDNSZoneResolver(target).zonehash(node);
        }
    }

    function setInterface(bytes32 node, bytes4 interfaceID, address implementer) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setInterface(node, interfaceID, implementer);
    }

    function interfaceImplementer(
        bytes32 node,
        bytes4 interfaceID
    ) external view virtual override returns (address result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IInterfaceResolver).interfaceId)) {
            return IInterfaceResolver(target).interfaceImplementer(node, interfaceID);
        }
    }

    function setName(bytes32 node, string calldata newName) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setName(node, newName);
    }

    function name(bytes32 node) external view virtual override returns (string memory result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(INameResolver).interfaceId)) {
            return INameResolver(target).name(node);
        }
    }

    function setPubkey(bytes32 node, bytes32 x, bytes32 y) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setPubkey(node, x, y);
    }

    function pubkey(bytes32 node) external view virtual override returns (bytes32 x, bytes32 y) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(IPubkeyResolver).interfaceId)) {
            return IPubkeyResolver(target).pubkey(node);
        }
    }

    function setText(bytes32 node, string calldata key, string calldata value) external virtual authorised(node) {
        IPublicResolver(getPassthroughTarget(node)).setText(node, key, value);
    }

    function text(bytes32 node, string calldata key) public view virtual override returns (string memory result) {
        address target = getPassthroughTarget(node);
        if (target.supportsInterface(type(ITextResolver).interfaceId)) {
            return ITextResolver(target).text(node, key);
        }
    }
}

// File: contracts/ensWildcardResolvers/WildcardResolverBase.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import { ENS } from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import { INameWrapper } from "@ensdomains/ens-contracts/contracts/wrapper/INameWrapper.sol";
import { IExtendedResolver } from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IExtendedResolver.sol";
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import { Context } from "@openzeppelin/contracts/utils/Context.sol";

import { ENSParentName } from "../libraries/ENSParentName.sol";
import { ENSByteUtils } from "../libraries/ENSByteUtils.sol";
import { BytesLib } from "../libraries/BytesLib.sol";

import { PassthroughResolver } from "./PassthroughResolver.sol";

abstract contract WildcardResolverBase is IExtendedResolver, Context, PassthroughResolver {
    using ENSByteUtils for address;
    using ENSByteUtils for bytes;
    using ENSParentName for bytes;
    using ERC165Checker for address;

    error RecordTypeNotSupported();
    error InvalidOperation();
    error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData);

    bytes4 public constant RESOLVER_SIGNATURE__ADDR = bytes4(keccak256(bytes("addr(bytes32)")));
    bytes4 public constant RESOLVER_SIGNATURE__ADDR_WITH_COINTYPE = bytes4(keccak256(bytes("addr(bytes32,uint256)")));
    bytes4 public constant RESOLVER_SIGNATURE__TEXT = bytes4(keccak256(bytes("text(bytes32,string)")));
    uint256 private constant COIN_TYPE_ETH = 60;

    ENS public immutable ensRegistry;
    INameWrapper public immutable ensNameWrapper;

    // dnsEncode(parentName) -> namehash(parentName)
    // ex: "test.eth" would be mapped as
    // 0x04746573740365746800 -> 0xeb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f1
    mapping(bytes => bytes32) internal parentEnsNodes;

    constructor(ENS _ensRegistry, INameWrapper _ensNameWrapper) {
        ensRegistry = _ensRegistry;
        ensNameWrapper = _ensNameWrapper;
    }

    function resolve(
        bytes calldata dnsEncodedName,
        bytes calldata resolverCalldata
    ) public view virtual override returns (bytes memory) {
        bytes4 resolverSignature = bytes4(resolverCalldata[:4]);

        if (resolverSignature == RESOLVER_SIGNATURE__ADDR) {
            address ethAddr = _resolveEthAddr(dnsEncodedName, resolverCalldata);
            return abi.encode(ethAddr);
        } else if (resolverSignature == RESOLVER_SIGNATURE__ADDR_WITH_COINTYPE) {
            (, uint256 coinType) = abi.decode(resolverCalldata[4:], (bytes32, uint256));
            if (coinType == COIN_TYPE_ETH) {
                address ethAddr = _resolveEthAddr(dnsEncodedName, resolverCalldata);
                return abi.encode(ethAddr.toBytes());
            } else {
                // Unsupported COIN_TYPE
                bytes memory emptyBytes;
                return abi.encode(emptyBytes);
            }
        } else if (resolverSignature == RESOLVER_SIGNATURE__TEXT) {
            string calldata key = _parseKeyFromCalldata(resolverCalldata);
            string memory result = _resolveTextRecord(dnsEncodedName, key, resolverCalldata);
            return abi.encode(result);
        }

        revert RecordTypeNotSupported();
    }

    function supportsInterface(bytes4 interfaceID) public view virtual override(PassthroughResolver) returns (bool) {
        return interfaceID == type(IExtendedResolver).interfaceId || PassthroughResolver.supportsInterface(interfaceID);
    }

    function _resolveWildcardEthAddr(
        bytes calldata childUtf8Encoded,
        bytes calldata parentDnsEncoded
    ) internal view virtual returns (address);

    function _resolveWildcardTextRecord(
        bytes calldata childUtf8Encoded,
        bytes calldata parentDnsEncoded,
        string calldata key
    ) internal view virtual returns (string memory);

    function _resolveEthAddr(
        bytes calldata dnsEncodedName,
        bytes calldata resolverCalldata
    ) private view returns (address result) {
        // Check if the caller is asking for a record on the parent name itself (non-wildcard query)
        (bool isParentName, bytes32 ensNode) = _isParentName(dnsEncodedName);

        if (isParentName) {
            // Try to resolve the parent name using the two `addr()` resolver variants
            result = addr(ensNode);
            if (result == address(0)) {
                bytes memory addrBytes = addr(ensNode, COIN_TYPE_ETH);
                if (addrBytes.length != 0) {
                    result = addrBytes.toAddress();
                }
            }
        } else {
            // Caller has issued a wildcard query. Defer to the concrete implementation of this contract
            (bytes calldata childUtf8Encoded, bytes calldata parentDnsEncoded) = dnsEncodedName.splitParentChildNames();
            ensNode = parentEnsNodes[parentDnsEncoded];
            result = _resolveWildcardEthAddr(childUtf8Encoded, parentDnsEncoded);
        }

        // No luck. If our fallback resolver also happens to implement the `resolve()` wildcard standard then we can try
        // that as a final option
        address passthrough = getPassthroughTarget(ensNode);
        if (result == address(0) && passthrough.supportsInterface(type(IExtendedResolver).interfaceId)) {
            try IExtendedResolver(passthrough).resolve(dnsEncodedName, resolverCalldata) returns (
                bytes memory encodedResult
            ) {
                (result) = abi.decode(encodedResult, (address));
                // Catch OffchainLookup and override sender param
            } catch (bytes memory err) {
                // The first 4 bytes of the ABI encoded error represent the error's signature
                // Slice those 4 bytes and get the data from the OffchainLookup error
                (
                    address sender,
                    string[] memory urls,
                    bytes memory callData,
                    bytes4 callbackFunction,
                    bytes memory extraData
                ) = abi.decode(BytesLib.slice(err, 4, err.length - 4), (address, string[], bytes, bytes4, bytes));
                revert OffchainLookup(
                    address(this),
                    urls,
                    callData,
                    this.resolveCallback.selector,
                    abi.encode(sender, callbackFunction, extraData)
                );
            }
        }
    }

    // Callback to contract that initially reverted OffchainLookup
    function resolveCallback(bytes calldata response, bytes calldata extraData) public returns (bytes memory) {
        (address inner, bytes4 innerCallbackFunction, bytes memory innerExtraData) = abi.decode(
            extraData,
            (address, bytes4, bytes)
        );
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory data) = inner.call(
            abi.encodeWithSelector(innerCallbackFunction, response, innerExtraData)
        );
        if (success) {
            return abi.decode(data, (bytes));
        }
        revert InvalidOperation();
    }

    function _resolveTextRecord(
        bytes calldata dnsEncodedName,
        string calldata key,
        bytes calldata resolverCalldata
    ) private view returns (string memory result) {
        // Check if the caller is asking for a record on the parent name itself (non-wildcard query)
        (bool isParentName, bytes32 ensNode) = _isParentName(dnsEncodedName);
        if (isParentName) {
            result = text(ensNode, key);
        } else {
            // Caller has issued a wildcard query. Defer to the concrete implementation of this contract
            (bytes calldata childUtf8Encoded, bytes calldata parentDnsEncoded) = dnsEncodedName.splitParentChildNames();
            ensNode = parentEnsNodes[parentDnsEncoded];
            result = _resolveWildcardTextRecord(childUtf8Encoded, parentDnsEncoded, key);
        }

        // No luck. If our fallback resolver also happens to implement the `resolve()` wildcard standard then we can try
        // that as a final option
        address passthrough = getPassthroughTarget(ensNode);
        if (bytes(result).length == 0 && passthrough.supportsInterface(type(IExtendedResolver).interfaceId)) {
            try IExtendedResolver(passthrough).resolve(dnsEncodedName, resolverCalldata) returns (
                bytes memory encodedResult
            ) {
                (result) = abi.decode(encodedResult, (string));
                // Catch OffchainLookup and override sender param
            } catch (bytes memory err) {
                // The first 4 bytes of the ABI encoded error represent the error's signature
                // Slice those 4 bytes and get the data from the OffchainLookup error
                (
                    address sender,
                    string[] memory urls,
                    bytes memory callData,
                    bytes4 callbackFunction,
                    bytes memory extraData
                ) = abi.decode(BytesLib.slice(err, 4, err.length - 4), (address, string[], bytes, bytes4, bytes));
                revert OffchainLookup(
                    address(this),
                    urls,
                    callData,
                    this.resolveCallback.selector,
                    abi.encode(sender, callbackFunction, extraData)
                );
            }
        }
    }

    function _parseKeyFromCalldata(bytes calldata resolverCalldata) private pure returns (string calldata key) {
        // ENS resolvers expect that the `key` for text queries is passed in via calldata.
        //
        // Until this is implemented in Solidity, we have to hand-pick the string out
        // of the calldata ourself: https://github.com/ethereum/solidity/issues/13518
        //
        // Here's the cleaner version once the above is implemented:
        //    (, string calldata key) = abi.decode(resolverCalldata[4:], (bytes32, string calldata));
        //
        // Reminder: the text resolver signature is `text(bytes32 ensNode, string [calldata] key)`
        //
        // Offset math:
        //    - 4 bytes for the function selector for `text(bytes32,string)`
        //    - 32 bytes for the `ensNode` as bytes32
        //    - 32 bytes to encode the offset to start of data part of the dynamic string parameter
        //         (see https://docs.soliditylang.org/en/v0.8.20/abi-spec.html#use-of-dynamic-types)
        //    - 32 bytes for the string's length: uint256(len(bytes(key_as_utf8_string)))
        //    - Remainder is the UTF8 encoding of the key, right-padded to a multiple of 32 bytes
        uint256 keyLengthOffset = 4 + 32 + 32;
        uint256 keyOffset = keyLengthOffset + 32;

        uint256 keyLength = abi.decode(resolverCalldata[keyLengthOffset:], (uint256));

        key = string(resolverCalldata[keyOffset:keyOffset + keyLength]);
    }

    function _isParentName(bytes calldata dnsEncodedName) internal view returns (bool, bytes32 ensNode) {
        ensNode = parentEnsNodes[dnsEncodedName];
        return (ensNode != bytes32(0), ensNode);
    }

    function _nodeOwner(bytes32 node) internal view returns (address) {
        address owner = ensRegistry.owner(node);
        if (owner == address(ensNameWrapper)) {
            owner = ensNameWrapper.ownerOf(uint256(node));
        }
        return owner;
    }
}

// File: contracts/libraries/BytesLib.sol



// SPDX-License-Identifier: MIT
// https://gist.github.com/rmeissner/76d6345796909ee41fb9f36fdaa4d15f

pragma solidity ^0.8.4;

library BytesLib {
    function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
        require(_length + 31 >= _length, "slice_overflow"); // solhint-disable-line custom-errors
        require(_bytes.length >= _start + _length, "slice_outOfBounds"); // solhint-disable-line custom-errors

        bytes memory tempBytes;

        // Check length is 0. `iszero` return 1 for `true` and 0 for `false`.
        // solhint-disable-next-line no-inline-assembly
        assembly {
            switch iszero(_length)
            case 0 {
                // Get a location of some free memory and store it in tempBytes as
                // Solidity does for memory variables.
                tempBytes := mload(0x40)

                // Calculate length mod 32 to handle slices that are not a multiple of 32 in size.
                let lengthmod := and(_length, 31)

                // tempBytes will have the following format in memory: <length><data>
                // When copying data we will offset the start forward to avoid allocating additional memory
                // Therefore part of the length area will be written, but this will be overwritten later anyways.
                // In case no offset is require, the start is set to the data region (0x20 from the tempBytes)
                // mc will be used to keep track where to copy the data to.
                let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
                let end := add(mc, _length)

                for {
                    // Same logic as for mc is applied & additionally the start offset specified for the method is added
                    let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
                } lt(mc, end) {
                    // increase `mc` and `cc` to read the next word from memory
                    mc := add(mc, 0x20)
                    cc := add(cc, 0x20)
                } {
                    // Copy the data from source (cc location) to the slice data (mc location)
                    mstore(mc, mload(cc))
                }

                // Store the length of the slice. This will overwrite any partial data that
                // was copied when having slices that are not a multiple of 32.
                mstore(tempBytes, _length)

                // update free-memory pointer
                // allocating the array padded to 32 bytes like the compiler does now
                // To set the used memory as a multiple of 32, add 31 to the actual memory usage (mc)
                // and remove the modulo 32 (the `and` with `not(31)`)
                mstore(0x40, and(add(mc, 31), not(31)))
            }
            // if we want a zero-length slice let's just return a zero-length array
            default {
                tempBytes := mload(0x40)
                // zero out the 32 bytes slice we are about to return
                // we need to do it because Solidity does not garbage collect
                mstore(tempBytes, 0)

                // update free-memory pointer
                // tempBytes uses 32 bytes in memory (even when empty) for the length.
                mstore(0x40, add(tempBytes, 0x20))
            }
        }

        return tempBytes;
    }
}

// File: contracts/libraries/ENSByteUtils.sol



// SPDX-License-Identifier: MIT

// solhint-disable-next-line max-line-length
// Source: https://github.com/ensdomains/ens-contracts/blob/340a6d05cd00d078ae40edbc58c139eb7048189a/contracts/resolvers/profiles/AddrResolver.sol

pragma solidity ^0.8.4;

/*
 * @dev Converts addresses to and from their byte-string representations
 */
library ENSByteUtils {
    // solhint-disable
    function toAddress(bytes memory b) internal pure returns (address payable a) {
        require(b.length == 20);
        assembly {
            a := div(mload(add(b, 32)), exp(256, 12)) // cspell:disable-line
        }
    }

    function toBytes(address a) internal pure returns (bytes memory b) {
        b = new bytes(20);
        assembly {
            mstore(add(b, 32), mul(a, exp(256, 12))) // cspell:disable-line
        }
    }
    // solhint-enable
}

// File: contracts/libraries/ENSParentName.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

library ENSParentName {
    /**
     * @dev Finds the parent name of a given ENS name, or the empty string if there is no parent.
     *      Assumes the given name is already a well-formed ENS name, and does not check for invalid input.
     * @param name A DNS-encoded name, e.g. 0x03666f6f03626172047465737400 for the name `foo.bar.test`
     * @return child The UTF8-encoded child label, e.g. 0x666f6f for `foo`
     * @return parent The DNS-encoded parent, e.g. 03626172047465737400 for `bar.test`
     */
    function splitParentChildNames(
        bytes calldata name
    ) internal pure returns (bytes calldata child, bytes calldata parent) {
        uint8 labelLength = uint8(name[0]);
        return (name[1:labelLength + 1], name[labelLength + 1:]);
    }
}