APE Price: $0.19 (-0.01%)

Contract

0xAd8FE2350f85bcbA946b69e2b1CA867AB2886070

Overview

APE Balance

Apechain LogoApechain LogoApechain Logo0 APE

APE Value

$0.00

More Info

Private Name Tags

Multichain Info

N/A
Transaction Hash
Block
From
To

There are no matching entries

Please try again later

Advanced mode:
Parent Transaction Hash Block From To
View All Internal Transactions

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ProxyDomaRecordUserFacet

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 200 runs

Other Settings:
istanbul EvmVersion
File 1 of 60 : ProxyDomaRecordUserFacet.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { LibProxyDomaRecord } from "../libraries/LibProxyDomaRecord.sol";
import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import { IDomaRecord } from "../../interfaces/IDomaRecord.sol";
import { CAIPUtils } from "../../utils/CAIPUtils.sol";
import { NameUtils } from "../../utils/NameUtils.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { SyntheticToken } from "../../SyntheticToken.sol";
import {
    CAPABILITY_REGISTRY_RECORDS_MANAGEMENT,
    CAPABILITY_DNS_RECORDS_MANAGEMENT
} from "../../doma-record/libraries/LibDoma.sol";

/**
 * @title ProxyDomaRecordUserFacet
 * @notice User-facing facet for the proxy DOMA record contract on tokenization chains.
 * @dev This facet handles user interactions including tokenization requests, ownership claims,
 * cross-chain bridging, and domain record management. It validates vouchers, manages fees,
 * and integrates with EIP-712 for signature verification. Supports both ownership tokens
 * and capability tokens with appropriate access controls.
 */
contract ProxyDomaRecordUserFacet is EIP712Upgradeable {
    using Strings for address;
    using Strings for uint256;
    using LibProxyDomaRecord for *;
    using ECDSA for bytes32;

    /**
     * @notice Tokenization voucher, obtained from a Registrar.
     * @param names List of names to tokenize.
     * @param nonce Unique nonce to prevent voucher reuse (replay attacks).
     * @param expiresAt Expiration date of the voucher (UNIX seconds).
     * @param ownerAddress Minted Ownership Token owner address. Must be equal to transaction sender.
     */
    struct TokenizationVoucher {
        IDomaRecord.NameInfo[] names;
        uint256 nonce;
        uint256 expiresAt;
        address ownerAddress;
    }

    /**
     * @notice Proof of Contacts voucher, obtained from a Registrar or Doma-provided storage.
     * @param registrantHandle Handle of a registrant in an off-chain storage.
     * @param proofSource Source of the proof-of-contacts voucher. 1 - Registrar, 2 - Doma.
     * @param nonce Unique nonce to prevent voucher reuse (replay attacks).
     * @param expiresAt Expiration date of the voucher (UNIX seconds).
     */
    struct ProofOfContactsVoucher {
        uint256 registrantHandle;
        IDomaRecord.ProofOfContactsSource proofSource;
        uint256 nonce;
        uint256 expiresAt;
    }

    string private constant _NAME_INFO_TYPE = "NameInfo(string sld,string tld)";
    bytes32 private constant _NAME_INFO_TYPE_HASH = keccak256(abi.encodePacked(_NAME_INFO_TYPE));

    string private constant _PROOF_OF_CONTACTS_VOUCHER_TYPE =
        "ProofOfContactsVoucher(uint256 registrantHandle,uint8 proofSource,uint256 nonce,uint256 expiresAt)";
    bytes32 private constant _PROOF_OF_CONTACTS_VOUCHER_TYPE_HASH =
        keccak256(abi.encodePacked(_PROOF_OF_CONTACTS_VOUCHER_TYPE));

    string private constant _TOKENIZATION_VOUCHER_TYPE =
        "TokenizationVoucher(NameInfo[] names,uint256 nonce,uint256 expiresAt,address ownerAddress)";
    bytes32 private constant _TOKENIZATION_VOUCHER_TYPE_HASH =
        keccak256(abi.encodePacked(_TOKENIZATION_VOUCHER_TYPE, _NAME_INFO_TYPE));

    /**
     * @notice Thrown when attempting to tokenize a name that is already tokenized.
     * @param sld The second-level domain of the name
     * @param tld The top-level domain of the name
     */
    error NameAlreadyTokenized(string sld, string tld);

    /**
     * @notice Thrown when a registrar ID doesn't match the expected registrar for an operation.
     * @param ianaId The provided registrar IANA ID
     * @param expectedIanaId The expected registrar IANA ID
     */
    error InvalidRegistrar(uint256 ianaId, uint256 expectedIanaId);

    /**
     * @notice Thrown when attempting to use an operation that is not supported for synthetic tokens.
     * @param tokenId The ID of the synthetic token
     */
    error SyntheticTokenNotSupported(uint256 tokenId);

    /**
     * @notice Thrown when attempting to set DNS records for a host that has an operational subdomain.
     * @param host The subdomain host label that is in use
     */
    error SubdomainHostInUse(string host);

    /**
     * @notice Thrown when a subdomain token is provided where a parent token is required.
     * @param tokenId The ID of the subdomain token
     */
    error InvalidTokenId(uint256 tokenId);

    /**
     * @notice Thrown when attempting to bridge to an unsupported target chain.
     * @param chainId The unsupported chain ID in CAIP-2 format
     */
    error UnsupportedTargetChain(string chainId);

    /**
     * @notice Thrown when a voucher signature is invalid or from an unauthorized signer.
     * @param signer The address that was recovered from the signature
     */
    error InvalidSigner(address signer);

    /**
     * @notice Thrown when a voucher has expired and cannot be used.
     * @param expiresAt The expiration timestamp of the voucher
     * @param currentTime The current block timestamp
     */
    error VoucherExpired(uint256 expiresAt, uint256 currentTime);

    /**
     * @notice Thrown when the price feed data is stale and cannot be trusted.
     * @param roundId The round ID from the price feed
     * @param updatedAt The timestamp when the price was last updated
     */
    error PriceFeedStalePrice(uint80 roundId, uint256 updatedAt);

    /**
     * @notice Thrown when the price feed returns an invalid price value.
     * @param price The invalid price value returned
     */
    error InvalidPrice(int256 price);

    /**
     * @notice Thrown when a native currency transfer fails.
     */
    error TransferFailed();

    /**
     * @notice Thrown when the provided fee doesn't match the expected fee for an operation.
     * @param expectedFee The expected fee amount in wei
     * @param providedFee The fee amount actually provided
     */
    error InvalidFee(uint256 expectedFee, uint256 providedFee);

    /**
     * @notice Thrown when a fee is provided for an operation that doesn't require payment.
     * @param operation The operation identifier
     * @param nameCount The number of names being processed
     */
    error FeeNotRequired(bytes32 operation, uint256 nameCount);

    /**
     * @notice Thrown when attempting to reuse a nonce that has already been consumed.
     * @param nonce The nonce that was already used
     */
    error NonceAlreadyUsed(uint256 nonce);

    /**
     * @notice Emitted when a fee is collected for an operation.
     * @param feeWei The fee amount collected in wei
     * @param feeUsdCents The equivalent fee amount in USD cents
     * @param correlationId The correlation ID for tracking the operation
     */
    event FeeCollected(uint256 feeWei, uint256 feeUsdCents, string correlationId);

    /**
     * @notice Emitted when a synthetic subdomain token is renounced by its owner.
     * @param tokenId The ID of the renounced token.
     * @param owner The address who renounced the token.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The ID of the group the token belonged to.
     */
    event SyntheticTokenRenounced(
        uint256 indexed tokenId,
        address indexed owner,
        uint256 indexed parentTokenId,
        uint256 groupId
    );

    /**
     * @notice Emitted when a synthetic subdomain token is revoked by parent owner.
     * @param tokenId The ID of the revoked token.
     * @param revokedFrom The address whose token was revoked.
     * @param revokedBy The parent token owner who revoked it.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The ID of the group the token belonged to.
     */
    event SyntheticTokenRevoked(
        uint256 indexed tokenId,
        address indexed revokedFrom,
        address indexed revokedBy,
        uint256 parentTokenId,
        uint256 groupId
    );

    /**
     * @notice Modifier to prevent operations on synthetic tokens that are not yet supported.
     * @dev Reverts with SyntheticTokenNotSupported error for synthetic tokens.
     * @param tokenId The ID of the token being operated on
     */
    modifier notSynthetic(uint256 tokenId) {
        if (LibProxyDomaRecord.isSyntheticToken(tokenId)) {
            revert SyntheticTokenNotSupported(tokenId);
        }
        _;
    }

    /**
     * @notice Modifier to verify that the caller is the owner of the specified token.
     * @dev Uses LibProxyDomaRecord to verify ownership before allowing operations.
     * @param tokenId The ID of the token to verify ownership for
     */
    modifier onlyTokenOwner(uint256 tokenId) {
        LibProxyDomaRecord._verifyTokenOwnership(tokenId, msg.sender);
        _;
    }

    /**
     * @notice Request tokenization of domain names using a signed voucher from a registrar.
     * @dev Validates the voucher signature, collects fees, and initiates cross-chain tokenization.
     * The voucher must be signed by an authorized registrar and not expired. Names are validated
     * for proper format and checked for existing tokenization on this chain.
     * @param voucher The tokenization voucher containing names and authorization details
     * @param signature The registrar's signature over the voucher data
     */
    function requestTokenization(
        TokenizationVoucher calldata voucher,
        bytes calldata signature
    ) external payable {
        _verifyNotExpiredVoucher(voucher.expiresAt);
        _verifyAndUpdateNonce(voucher.nonce);
        if (msg.sender != voucher.ownerAddress) {
            revert LibProxyDomaRecord.InvalidOwnerAddress(msg.sender, voucher.ownerAddress);
        }

        uint256 nameCount = voucher.names.length;
        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        _collectFee(LibProxyDomaRecord.REQUEST_TOKENIZATION_OPERATION, nameCount, correlationId);

        bytes32 voucherHash = _hashTokenizationVoucher(voucher);
        bytes32 digest = _hashTypedDataV4(voucherHash);
        uint256 registrarIanaId = _verifyRegistrarSignature(digest, signature);

        for (uint256 i = 0; i < nameCount; i++) {
            IDomaRecord.NameInfo memory name = voucher.names[i];

            // Validate SLD label format
            NameUtils.ensureValidLabel(name.sld);

            // Validate TLD format (can be eTLD with dots, but must be valid domain name)
            NameUtils.ensureValidName(name.tld);

            // Optimistic check that name isn't already tokenized
            // It's not a foolproof, since name might be tokenized on another chain, or be in progress
            // However, this would help to reject repeated tokenization requests on the same chain
            uint256 nameId = NameUtils.namehash(string(abi.encodePacked(name.sld, ".", name.tld)));

            if (LibProxyDomaRecord.proxyDomaRecordStorage().ownershipToken.exists(nameId)) {
                revert NameAlreadyTokenized(name.sld, name.tld);
            }
        }

        bytes memory initiateTokenizationCalldata = abi.encodeCall(
            IDomaRecord.initiateTokenization,
            (
                registrarIanaId,
                voucher.names,
                CAIPUtils.caip2Local(),
                voucher.ownerAddress.toChecksumHexString(),
                correlationId
            )
        );

        // Generate a unique nonce key to prevent replays of the same message
        uint256 nonceKey = uint256(
            keccak256(abi.encodePacked(initiateTokenizationCalldata, block.number))
        );
        LibProxyDomaRecord._relayMessage(initiateTokenizationCalldata, correlationId, nonceKey);
    }

    /**
     * @notice Claim ownership of a domain name using proof of contacts voucher.
     * @dev Allows token owners to establish claim over their domain by providing signed proof
     * from either the registrar or DOMA system. Validates voucher signature, collects fees,
     * and initiates cross-chain ownership claim process.
     * @param tokenId The ID of the ownership token being claimed
     * @param proofOfContactsVoucher The voucher containing proof of contact information
     * @param signature The signature over the voucher (from registrar or DOMA)
     */
    function claimOwnership(
        uint256 tokenId,
        ProofOfContactsVoucher calldata proofOfContactsVoucher,
        bytes calldata signature
    ) public payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        _verifyNotExpiredVoucher(proofOfContactsVoucher.expiresAt);
        _verifyAndUpdateNonce(proofOfContactsVoucher.nonce);

        bytes32 voucherHash = _hashProofOfContactsVoucher(proofOfContactsVoucher);
        bytes32 digest = _hashTypedDataV4(voucherHash);

        if (proofOfContactsVoucher.proofSource == IDomaRecord.ProofOfContactsSource.REGISTRAR) {
            uint256 registrarIanaId = _verifyRegistrarSignature(digest, signature);
            uint256 tokenRegistrar = LibProxyDomaRecord
                .proxyDomaRecordStorage()
                .ownershipToken
                .registrarOf(tokenId);
            if (tokenRegistrar != registrarIanaId) {
                revert InvalidRegistrar(registrarIanaId, tokenRegistrar);
            }
        } else {
            _verifyDomaSignature(digest, signature);
        }

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        _collectFee(LibProxyDomaRecord.CLAIM_OWNERSHIP_OPERATION, 1, correlationId);

        bytes memory claimOwnershipCalldata = abi.encodeCall(
            IDomaRecord.claimOwnership,
            (
                tokenId.toString(),
                CAIPUtils.caip2Local(),
                msg.sender.toChecksumHexString(),
                proofOfContactsVoucher.proofSource,
                proofOfContactsVoucher.registrantHandle,
                correlationId
            )
        );

        LibProxyDomaRecord._relayMessage(claimOwnershipCalldata, correlationId, tokenId);
    }

    /**
     * @notice Deprecated overload with isSynthetic parameter - this parameter is ignored and will be removed in a future version.
     * @dev This overload is maintained for backward compatibility. Use claimOwnership(tokenId, proofOfContactsVoucher, signature) instead.
     */
    function claimOwnership(
        uint256 tokenId,
        bool, // isSynthetic?
        ProofOfContactsVoucher calldata proofOfContactsVoucher,
        bytes calldata signature
    ) external payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        claimOwnership(tokenId, proofOfContactsVoucher, signature);
    }

    /**
     * @notice Deprecated overload with isSynthetic parameter - this parameter is ignored and will be removed in a future version.
     * @dev This overload is maintained for backward compatibility. Use bridge(tokenId, targetChainId, targetOwnerAddress) instead.
     */
    function bridge(
        uint256 tokenId,
        bool, // isSynthetic
        string calldata targetChainId,
        string calldata targetOwnerAddress
    ) external payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        bridge(tokenId, targetChainId, targetOwnerAddress);
    }

    /**
     * @notice Bridge a domain token to another supported chain.
     * @dev Burns the token on this chain and initiates minting on the target chain.
     * Validates target chain support, transfer lock status, and token expiration.
     * Collects fees and relays the bridge request to the DOMA chain.
     * @param tokenId The ID of the ownership token to bridge
     * @param targetChainId The CAIP-2 chain ID of the destination chain
     * @param targetOwnerAddress The owner address on the target chain
     */
    function bridge(
        uint256 tokenId,
        string calldata targetChainId,
        string calldata targetOwnerAddress
    ) public payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        // Verify the target chain ID is supported
        if (!isTargetChainSupported(targetChainId)) {
            revert UnsupportedTargetChain(targetChainId);
        }

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();

        _collectFee(LibProxyDomaRecord.BRIDGE_OPERATION, 1, correlationId);

        bool isLocked = LibProxyDomaRecord.proxyDomaRecordStorage().ownershipToken.lockStatusOf(
            tokenId
        );
        if (isLocked) {
            revert LibProxyDomaRecord.TransferLocked(tokenId);
        }

        uint256 expiresAt = LibProxyDomaRecord.proxyDomaRecordStorage().ownershipToken.expirationOf(
            tokenId
        );
        if (expiresAt < block.timestamp) {
            revert LibProxyDomaRecord.NameTokenHasExpired(tokenId, expiresAt);
        }

        LibProxyDomaRecord._burnOwnershipToken(tokenId, correlationId);

        bytes memory bridgeCalldata = abi.encodeCall(
            IDomaRecord.bridge,
            (tokenId.toString(), targetChainId, targetOwnerAddress, correlationId)
        );

        LibProxyDomaRecord._relayMessage(bridgeCalldata, correlationId, tokenId);
    }

    /**
     * @notice Convert an ownership token to a synthetic ownership token.
     * @dev Burns the ownership token on this chain and sends a cross-chain message to DOMA chain
     * to set the synthetic flag. Once confirmed, a synthetic token will be minted to the owner.
     * The synthetic token maintains the same tokenId, expiration, and capabilities as the original.
     * @param tokenId The ID of the ownership token to convert
     */
    function convertToSynthetic(uint256 tokenId) external payable onlyTokenOwner(tokenId) {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();

        _collectFee(LibProxyDomaRecord.CONVERT_TO_SYNTHETIC_OPERATION, 1, correlationId);

        LibProxyDomaRecord._validateOwnershipTokenForConversion(tokenId, msg.sender);

        address tokenOwner = s.ownershipToken.ownerOf(tokenId);

        bytes memory convertToSyntheticCalldata = abi.encodeCall(
            IDomaRecord.convertToSynthetic,
            (tokenId, CAIPUtils.caip2Local(), tokenOwner.toChecksumHexString(), correlationId)
        );

        LibProxyDomaRecord._relayMessage(convertToSyntheticCalldata, correlationId, tokenId);
    }

    /**
     * @notice Convert a synthetic ownership token back to a regular ownership token.
     * @dev Burns the synthetic token on this chain and sends a cross-chain message to DOMA chain
     * to update the synthetic flag. Once confirmed, a regular ownership token will be minted.
     * Requires that the synthetic token has no active subdomains (subdomainCount == 0).
     * @param tokenId The ID of the synthetic token to convert
     */
    function convertToOwnership(uint256 tokenId) external payable onlyTokenOwner(tokenId) {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();

        _collectFee(LibProxyDomaRecord.CONVERT_TO_OWNERSHIP_OPERATION, 1, correlationId);

        LibProxyDomaRecord._validateSyntheticTokenForConversion(tokenId, msg.sender);

        address tokenOwner = s.syntheticToken.ownerOf(tokenId);

        bytes memory convertToOwnershipCalldata = abi.encodeCall(
            IDomaRecord.convertToOwnership,
            (tokenId, CAIPUtils.caip2Local(), tokenOwner.toChecksumHexString(), correlationId)
        );

        LibProxyDomaRecord._relayMessage(convertToOwnershipCalldata, correlationId, tokenId);
    }

    /**
     * @notice Deprecated overload with isSynthetic parameter - this parameter is ignored and will be removed in a future version.
     * @dev This overload is maintained for backward compatibility. Use requestDetokenization(tokenId) instead.
     */
    function requestDetokenization(
        uint256 tokenId,
        bool
    ) external notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        requestDetokenization(tokenId);
    }

    /**
     * @notice Request detokenization of a domain name by the owner.
     * @dev Initiates the detokenization process on the DOMA chain, which will remove
     * the domain from the protocol and return it to traditional DNS management.
     * Only the token owner can request detokenization.
     * @param tokenId The ID of the ownership token to detokenize
     */
    function requestDetokenization(
        uint256 tokenId
    ) public notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        bytes memory ownerDetokenizeCalldata = abi.encodeCall(
            IDomaRecord.ownerDetokenize,
            (
                tokenId.toString(),
                CAIPUtils.caip2Local(),
                msg.sender.toChecksumHexString(),
                correlationId
            )
        );

        LibProxyDomaRecord._relayMessage(ownerDetokenizeCalldata, correlationId, tokenId);
    }

    /**
     * @notice Deprecated overload with isSynthetic parameter - this parameter is ignored and will be removed in a future version.
     * @dev This overload is maintained for backward compatibility. Use setNameservers(tokenId, nameservers) instead.
     */
    function setNameservers(
        uint256 tokenId,
        string[] calldata nameservers,
        bool
    ) external payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        setNameservers(tokenId, nameservers);
    }

    /**
     * @notice Update nameservers for a domain.
     * @dev Requires CAPABILITY_REGISTRY_RECORDS_MANAGEMENT. Relays change to Doma Chain.
     * @param tokenId The ownership token ID.
     * @param nameservers List of nameserver hostnames.
     */
    function setNameservers(
        uint256 tokenId,
        string[] calldata nameservers
    ) public payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        _validateCapability(tokenId, CAPABILITY_REGISTRY_RECORDS_MANAGEMENT);

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        _collectFee(LibProxyDomaRecord.SET_NAMESERVERS_OPERATION, 1, correlationId);

        bytes memory setNameserversCalldata = abi.encodeCall(
            IDomaRecord.setNameservers,
            (tokenId, nameservers, correlationId)
        );

        LibProxyDomaRecord._relayMessage(setNameserversCalldata, correlationId, tokenId);
    }

    /**
     * @notice Deprecated overload with isSynthetic parameter - this parameter is ignored and will be removed in a future version.
     * @dev This overload is maintained for backward compatibility. Use setDSKeys(tokenId, dsKeys) instead.
     */
    function setDSKeys(
        uint256 tokenId,
        IDomaRecord.DSKey[] calldata dsKeys,
        bool
    ) external payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        setDSKeys(tokenId, dsKeys);
    }

    /**
     * @notice Update DNSSEC DS keys for a domain.
     * @dev Requires CAPABILITY_REGISTRY_RECORDS_MANAGEMENT. Relays change to Doma Chain.
     * @param tokenId The ownership token ID.
     * @param dsKeys Array of DS key records.
     */
    function setDSKeys(
        uint256 tokenId,
        IDomaRecord.DSKey[] calldata dsKeys
    ) public payable notSynthetic(tokenId) onlyTokenOwner(tokenId) {
        _validateCapability(tokenId, CAPABILITY_REGISTRY_RECORDS_MANAGEMENT);

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        _collectFee(LibProxyDomaRecord.SET_DS_KEYS_OPERATION, 1, correlationId);

        bytes memory setDSKeysCalldata = abi.encodeCall(
            IDomaRecord.setDSKeys,
            (tokenId, dsKeys, correlationId)
        );

        LibProxyDomaRecord._relayMessage(setDSKeysCalldata, correlationId, tokenId);
    }

    /**
     * @notice Deprecated overload with isSynthetic parameter - this parameter is ignored and will be removed in a future version.
     * @dev This overload is maintained for backward compatibility. Use setDNSRRSet(tokenId, host, recordType, ttl, records) instead.
     */
    function setDNSRRSet(
        uint256 tokenId,
        string calldata host,
        string calldata recordType,
        uint32 ttl,
        string[] calldata records,
        bool
    ) external payable onlyTokenOwner(tokenId) {
        setDNSRRSet(tokenId, host, recordType, ttl, records);
    }

    /**
     * @notice Set DNS resource record set for a domain.
     * @dev Requires CAPABILITY_DNS_RECORDS_MANAGEMENT. Relays change to Doma Chain.
     * Works for both ownership and synthetic tokens (including subdomain tokens).
     * Host must be lowercase (enforced by validation).
     * @param tokenId The token ID (ownership, synthetic root, or synthetic subdomain).
     * @param host The hostname relative to the token (e.g., "www" or "" for apex).
     * @param recordType DNS record type (e.g., "A", "AAAA", "CNAME").
     * @param ttl Time-to-live in seconds.
     * @param records Array of record values. Empty array deletes the RRSet.
     */
    function setDNSRRSet(
        uint256 tokenId,
        string calldata host,
        string calldata recordType,
        uint32 ttl,
        string[] calldata records
    ) public payable {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        // Empty string is allowed for subdomain apex
        if (bytes(host).length > 0) {
            NameUtils.ensureValidHost(host);
        }

        bool isSynthetic = LibProxyDomaRecord.isSyntheticToken(tokenId);
        uint256 parentTokenId = isSynthetic ? s.syntheticToken.getParentTokenId(tokenId) : 0;

        // If using a parent token, check if a subdomain exists for this host
        // If subdomain exists and is owned by someone else, reject the operation
        if (isSynthetic && parentTokenId == 0) {
            // This is a parent synthetic token - check for subdomain conflicts
            // Extract the last label (immediate child) from the host
            string memory immediateChild = _getLastLabel(host);

            // If an operational subdomain exists for this label, caller must use the subdomain token
            if (s.syntheticToken.isSubdomainOperational(tokenId, immediateChild)) {
                revert SubdomainHostInUse(immediateChild);
            }
        }

        // Verify caller owns the token
        LibProxyDomaRecord._verifyTokenOwnership(tokenId, msg.sender);

        // Validate capability on the token
        _validateCapability(tokenId, CAPABILITY_DNS_RECORDS_MANAGEMENT);

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        _collectFee(LibProxyDomaRecord.SET_DNS_RRSET_OPERATION, 1, correlationId);

        // Determine parent tokenId and full host for DOMA chain
        uint256 parentTokenIdForDoma;
        string memory fullHost;

        if (isSynthetic && parentTokenId != 0) {
            // This is a subdomain token
            string memory subdomainHost = s.syntheticToken.getSyntheticData(tokenId).host;
            parentTokenIdForDoma = parentTokenId;

            if (bytes(host).length == 0) {
                // Setting DNS for the subdomain apex
                fullHost = subdomainHost;
            } else {
                // Setting DNS for a nested record under the subdomain
                fullHost = string(abi.encodePacked(host, ".", subdomainHost));
            }
        } else {
            // This is either an ownership token or a synthetic root token
            parentTokenIdForDoma = tokenId;
            fullHost = host;
        }

        bytes memory setDNSRRSetCalldata = abi.encodeCall(
            IDomaRecord.setDNSRRSet,
            (parentTokenIdForDoma, fullHost, recordType, ttl, records, correlationId)
        );

        LibProxyDomaRecord._relayMessage(setDNSRRSetCalldata, correlationId, parentTokenIdForDoma);
    }

    // View functions

    /**
     * @notice Get the fee in USD cents for a specific operation.
     * @param operation The operation identifier to query
     * @return The fee amount in USD cents
     */
    function feesUSDCents(bytes32 operation) public view returns (uint256) {
        return LibProxyDomaRecord.proxyDomaRecordStorage().feesUSDCents[operation];
    }

    /**
     * @notice Check if a target chain is supported for bridging operations.
     * @param targetChainId The CAIP-2 chain identifier to check
     * @return Whether the target chain is supported
     */
    function isTargetChainSupported(string calldata targetChainId) public view returns (bool) {
        return LibProxyDomaRecord.proxyDomaRecordStorage().supportedTargetChains[targetChainId];
    }

    /**
     * @notice Convert a USD cent amount to the equivalent native token amount.
     * @dev Uses Chainlink price feed to get current exchange rate. Validates price feed data.
     * @param feeUSDCents The fee amount in USD cents
     * @return nativeFee The equivalent fee in native token wei
     */
    function getNativePrice(uint256 feeUSDCents) public view returns (uint256 nativeFee) {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage _storage = LibProxyDomaRecord
            .proxyDomaRecordStorage();
        (uint80 roundId, int256 price, , uint256 updatedAt, ) = _storage
            .priceFeed
            .latestRoundData();
        _validatePriceFeedResponse(roundId, price, updatedAt);
        return _getNativePrice(feeUSDCents, price);
    }

    /**
     * @notice Get the fee in native tokens for a specific operation.
     * @dev Combines USD cent fee lookup with current price conversion.
     * @param operation The operation identifier to query
     * @return The fee amount in native token wei (0 if operation has no fee)
     */
    function getOperationFeeInNative(bytes32 operation) public view returns (uint256) {
        if (!LibProxyDomaRecord._isValidOperation(operation)) {
            revert LibProxyDomaRecord.InvalidOperation(operation);
        }

        uint256 feeUSDCents = feesUSDCents(operation);
        if (feeUSDCents == 0) {
            return 0;
        }

        // Fetch the native token fee for the operation
        uint256 nativeFee = getNativePrice(feeUSDCents);
        return nativeFee;
    }

    function _hashTokenizationVoucher(
        TokenizationVoucher calldata voucher
    ) internal pure returns (bytes32) {
        bytes32[] memory namesHashes = new bytes32[](voucher.names.length);

        for (uint256 i = 0; i < voucher.names.length; i++) {
            IDomaRecord.NameInfo memory name = voucher.names[i];
            namesHashes[i] = keccak256(
                abi.encode(
                    _NAME_INFO_TYPE_HASH,
                    keccak256(bytes(name.sld)),
                    keccak256(bytes(name.tld))
                )
            );
        }

        return
            keccak256(
                abi.encode(
                    _TOKENIZATION_VOUCHER_TYPE_HASH,
                    keccak256(abi.encodePacked(namesHashes)),
                    voucher.nonce,
                    voucher.expiresAt,
                    voucher.ownerAddress
                )
            );
    }

    function _hashProofOfContactsVoucher(
        ProofOfContactsVoucher calldata voucher
    ) internal pure returns (bytes32) {
        uint8 proofSource = uint8(voucher.proofSource);
        return
            keccak256(
                abi.encode(
                    _PROOF_OF_CONTACTS_VOUCHER_TYPE_HASH,
                    voucher.registrantHandle,
                    proofSource,
                    voucher.nonce,
                    voucher.expiresAt
                )
            );
    }

    function _verifyNotExpiredVoucher(uint256 expiresAt) internal view {
        if (expiresAt < block.timestamp) {
            revert VoucherExpired(expiresAt, block.timestamp);
        }
    }

    function _verifyAndUpdateNonce(uint256 nonce) internal {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage _storage = LibProxyDomaRecord
            .proxyDomaRecordStorage();
        if (_storage.nonces[nonce] == true) {
            revert NonceAlreadyUsed(nonce);
        }

        _storage.nonces[nonce] = true;
    }

    function _verifyRegistrarSignature(
        bytes32 digest,
        bytes calldata signature
    ) internal view returns (uint256) {
        address signatureSigner = digest.recover(signature);

        LibProxyDomaRecord.ProxyDomaRecordStorage storage _storage = LibProxyDomaRecord
            .proxyDomaRecordStorage();
        uint256 ianaId = _storage.registrarSigners[signatureSigner];
        if (ianaId == 0) {
            revert InvalidSigner(signatureSigner);
        }

        return ianaId;
    }

    function _verifyDomaSignature(bytes32 digest, bytes calldata signature) internal view {
        address signatureSigner = digest.recover(signature);

        LibProxyDomaRecord.ProxyDomaRecordStorage storage _storage = LibProxyDomaRecord
            .proxyDomaRecordStorage();
        if (!_storage.domaSigners[signatureSigner]) {
            revert InvalidSigner(signatureSigner);
        }
    }

    function _collectFee(
        bytes32 operation,
        uint256 nameCount,
        string memory correlationId
    ) internal {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage _storage = LibProxyDomaRecord
            .proxyDomaRecordStorage();
        uint256 feeUSDCents = _storage.feesUSDCents[operation] * nameCount;
        if (feeUSDCents == 0) {
            if (msg.value > 0) {
                revert FeeNotRequired(operation, nameCount);
            }
            return;
        }

        (uint80 latestRoundId, int256 price, , uint256 updatedAt, ) = _storage
            .priceFeed
            .latestRoundData();
        _validatePriceFeedResponse(latestRoundId, price, updatedAt);
        uint256 nativeFee = _getNativePrice(feeUSDCents, price);

        if (msg.value != nativeFee) {
            // Try to use previous round data to calculate the fee
            // This could happen if transaction spent to long in the mempool, or round data change while transaction was being constructed/signed
            uint80 previousRoundId = _getPreviousRoundId(latestRoundId);
            (, int256 previousRoundPrice, , uint256 previousRoundUpdatedAt, ) = _storage
                .priceFeed
                .getRoundData(previousRoundId);
            _validatePriceFeedResponse(previousRoundId, previousRoundPrice, previousRoundUpdatedAt);

            nativeFee = _getNativePrice(feeUSDCents, previousRoundPrice);

            if (msg.value != nativeFee) {
                revert InvalidFee(nativeFee, msg.value);
            }
        }

        if (_storage.treasury == address(0)) revert LibProxyDomaRecord.ZeroAddress();

        (bool success, ) = payable(_storage.treasury).call{ value: msg.value }("");
        if (!success) revert TransferFailed();

        emit FeeCollected(msg.value, feeUSDCents, correlationId);
    }

    function _getNativePrice(
        uint256 feeUSDCents,
        int256 price
    ) internal view returns (uint256 nativeFee) {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage _storage = LibProxyDomaRecord
            .proxyDomaRecordStorage();
        uint8 decimals = _storage.priceFeed.decimals();

        if (price <= 0) revert InvalidPrice(price);

        uint256 currentPriceInUSD = uint256(price);

        // feeUSDCents is in cents (2 decimal places)
        // We need to normalize based on the price feed decimals
        if (decimals >= 2) {
            // If price feed has more decimals than our fee (which is in cents with 2 decimals)
            // Multiply fee by 10^(decimals-2) to match price feed precision
            uint256 normalizedFee = feeUSDCents * (10 ** (decimals - 2));
            nativeFee = (normalizedFee * 1e18) / currentPriceInUSD;
        } else {
            // If price feed has fewer decimals than our fee
            // Multiply price by 10^(2-decimals) to match fee precision
            uint256 normalizedPrice = currentPriceInUSD * (10 ** (2 - decimals));
            nativeFee = (feeUSDCents * 1e18) / normalizedPrice;
        }

        return nativeFee;
    }

    function _validatePriceFeedResponse(
        uint80 roundId,
        int256 price,
        uint256 updatedAt
    ) internal view {
        if (price <= 0) revert InvalidPrice(price);

        if (updatedAt < block.timestamp - 1 days) {
            revert PriceFeedStalePrice(roundId, updatedAt);
        }
    }

    function _getPreviousRoundId(uint80 roundId) internal pure returns (uint80) {
        uint80 phaseId = roundId >> 64;
        uint64 aggregatorRoundId = uint64(roundId);

        // If it's the first round on a phase, there's no previous round
        // Unlikely to happen in actual deployments, but could happen in testnets
        // Since we don't know last round of the previous phase, we return the current round as a fallback
        if (aggregatorRoundId <= 1) {
            return roundId;
        }

        return (phaseId << 64) | (aggregatorRoundId - 1);
    }

    /**
     * @notice Get the contract version.
     * @return The version string of this contract
     */
    function version() external pure returns (string memory) {
        return "1.1.0";
    }

    /**
     * @notice Override EIP712 name to avoid initialization requirement.
     * @dev Returns the domain name used for EIP-712 signature verification.
     * @return The EIP-712 domain name
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Name() internal pure override returns (string memory) {
        return "DOMA";
    }

    /**
     * @notice Override EIP712 version to avoid initialization requirement.
     * @dev Returns the domain version used for EIP-712 signature verification.
     * @return The EIP-712 domain version
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Version() internal pure override returns (string memory) {
        return "1";
    }

    /**
     * @notice Override eip712Domain to work without initialization (EIP-5267 compliance).
     * @dev Returns the EIP-712 domain parameters for signature verification.
     * @return fields The fields that are set in the domain
     * @return name The EIP-712 domain name
     * @return domainVersion The EIP-712 domain version
     * @return chainId The chain ID for the domain
     * @return verifyingContract The address of this contract
     * @return salt The domain salt (unused)
     * @return extensions The domain extensions (unused)
     */
    function eip712Domain()
        public
        view
        virtual
        override
        returns (
            bytes1 fields,
            string memory name,
            string memory domainVersion,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @notice Get the REQUEST_TOKENIZATION_OPERATION constant
     * @return The operation hash for request tokenization
     */
    // solhint-disable-next-line func-name-mixedcase
    function REQUEST_TOKENIZATION_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.REQUEST_TOKENIZATION_OPERATION;
    }

    /**
     * @notice Get the CLAIM_OWNERSHIP_OPERATION constant
     * @return The operation hash for claim ownership
     */
    // solhint-disable-next-line func-name-mixedcase
    function CLAIM_OWNERSHIP_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.CLAIM_OWNERSHIP_OPERATION;
    }

    /**
     * @notice Get the BRIDGE_OPERATION constant
     * @return The operation hash for bridge
     */
    // solhint-disable-next-line func-name-mixedcase
    function BRIDGE_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.BRIDGE_OPERATION;
    }

    /**
     * @notice Get the SET_NAMESERVERS_OPERATION constant
     * @return The operation hash for set nameservers
     */
    // solhint-disable-next-line func-name-mixedcase
    function SET_NAMESERVERS_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.SET_NAMESERVERS_OPERATION;
    }

    /**
     * @notice Get the SET_DS_KEYS_OPERATION constant
     * @return The operation hash for set DS keys
     */
    // solhint-disable-next-line func-name-mixedcase
    function SET_DS_KEYS_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.SET_DS_KEYS_OPERATION;
    }

    /**
     * @notice Get the SET_DNS_RRSET_OPERATION constant
     * @return The operation hash for set DNS RRSet
     */
    // solhint-disable-next-line func-name-mixedcase
    function SET_DNS_RRSET_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.SET_DNS_RRSET_OPERATION;
    }

    /**
     * @notice Get the CONVERT_TO_SYNTHETIC_OPERATION constant
     * @return The operation hash for convert to synthetic
     */
    // solhint-disable-next-line func-name-mixedcase
    function CONVERT_TO_SYNTHETIC_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.CONVERT_TO_SYNTHETIC_OPERATION;
    }

    /**
     * @notice Get the CONVERT_TO_OWNERSHIP_OPERATION constant
     * @return The operation hash for convert to ownership
     */
    // solhint-disable-next-line func-name-mixedcase
    function CONVERT_TO_OWNERSHIP_OPERATION() external pure returns (bytes32) {
        return LibProxyDomaRecord.CONVERT_TO_OWNERSHIP_OPERATION;
    }

    /**
     * @notice Validates that a token has the required capability
     * @dev For REGISTRY_RECORDS_MANAGEMENT: checks registrar capabilities
     *      For DNS_RECORDS_MANAGEMENT: checks domain capabilities
     * @param tokenId The token ID to check
     * @param requiredCapability The required capability bit mask
     */
    function _validateCapability(uint256 tokenId, uint256 requiredCapability) private view {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage ds = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        uint256 capabilities;

        if (requiredCapability == CAPABILITY_REGISTRY_RECORDS_MANAGEMENT) {
            // For registry records (nameservers, DS keys): check registrar capabilities
            uint256 registrarIanaId;
            if (LibProxyDomaRecord.isSyntheticToken(tokenId)) {
                registrarIanaId = ds.syntheticToken.registrarOf(tokenId);
            } else {
                registrarIanaId = ds.ownershipToken.registrarOf(tokenId);
            }
            capabilities = ds.registrarCapabilities[registrarIanaId];
        } else if (requiredCapability == CAPABILITY_DNS_RECORDS_MANAGEMENT) {
            // For DNS records: check domain capabilities
            capabilities = LibProxyDomaRecord._getDomainCapabilities(tokenId);
        }

        if ((capabilities & requiredCapability) != requiredCapability) {
            revert LibProxyDomaRecord.InsufficientCapabilities(tokenId, requiredCapability);
        }
    }

    /**
     * @notice Mint a synthetic subdomain token directly without cross-chain call.
     * @dev Mints subdomain token on this chain, bypassing DomaRecord on DOMA chain.
     *      Validates parent ownership, depth limits, expiration.
     *      Only CAPABILITY_DNS_RECORDS_MANAGEMENT (4) is allowed for subdomains.
     *      Uses new SyntheticSubdomainMinted event without sld/tld (use parentTokenId to lookup).
     * @param parentTokenId The ID of the parent synthetic token
     * @param host The subdomain host label (e.g., "www" or "api")
     * @param capabilities Capability flags (must be CAPABILITY_DNS_RECORDS_MANAGEMENT = 4)
     * @param expiresAt Expiration timestamp for the subdomain token (0 to inherit from parent)
     * @param revocable Whether the parent can revoke this subdomain
     * @param groupId Group identifier for batch operations
     * @param receiver The address to receive the minted subdomain token
     * @return The newly minted synthetic subdomain token ID
     */
    function mintSyntheticSubdomain(
        uint256 parentTokenId,
        string calldata host,
        uint256 capabilities,
        uint256 expiresAt,
        bool revocable,
        uint256 groupId,
        address receiver
    ) external payable onlyTokenOwner(parentTokenId) returns (uint256) {
        // Validate subdomain extraction parameters
        LibProxyDomaRecord._validateSubdomainExtraction(parentTokenId, host, capabilities);

        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();

        // Mint subdomain token directly (no cross-chain call)
        return
            s.syntheticToken.mintSyntheticSubdomain(
                SyntheticToken.SyntheticSubdomainMintInfo({
                    receiver: receiver,
                    revocable: revocable,
                    expiresAt: expiresAt,
                    capabilities: capabilities,
                    host: host,
                    parentTokenId: parentTokenId,
                    groupId: groupId
                }),
                correlationId
            );
    }

    /**
     * @notice Renew a synthetic subdomain token.
     * @dev Can only be called by the parent token owner.
     *      Validates that new expiry extends current expiry and doesn't exceed parent expiry.
     *      Only works for subdomains (tokens with parentTokenId != 0).
     * @param tokenId The ID of the subdomain token to renew.
     * @param expiresAt The new expiration timestamp (must be > current expiry and <= parent expiry).
     */
    function renewSubdomain(uint256 tokenId, uint256 expiresAt) external payable {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        // Get subdomain's parent tokenId
        uint256 parentTokenId = s.syntheticToken.getParentTokenId(tokenId);

        // Verify this is a subdomain (not a root token)
        if (parentTokenId == 0) {
            revert LibProxyDomaRecord.NotSubdomain(tokenId);
        }

        // Verify caller is the parent owner
        LibProxyDomaRecord._verifyTokenOwnership(parentTokenId, msg.sender);

        string memory correlationId = LibProxyDomaRecord._useCorrelationId();

        // Renew the subdomain (SyntheticToken.renew will validate expiry rules)
        s.syntheticToken.renew(tokenId, expiresAt, correlationId);
    }

    /**
     * @notice Renounce ownership of a synthetic subdomain token.
     * @dev Burns the subdomain token and makes it available for re-minting.
     *      Can only be called by the subdomain token owner.
     *      The subdomain must not be in a locked group.
     * @param tokenId The ID of the subdomain token to renounce.
     */
    function renounceSynthetic(uint256 tokenId) external onlyTokenOwner(tokenId) {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        // Get token data
        SyntheticToken.SyntheticData memory data = s.syntheticToken.getSyntheticData(tokenId);

        // Verify this is a subdomain (not a root token)
        if (data.parentTokenId == 0) {
            revert LibProxyDomaRecord.NotSubdomain(tokenId);
        }

        // Verify group not revoked
        if (s.syntheticToken.isGroupRevoked(data.parentTokenId, data.groupId)) {
            revert LibProxyDomaRecord.GroupRevoked(data.parentTokenId, data.groupId);
        }

        // Generate correlation ID and burn
        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        s.syntheticToken.burnSynthetic(tokenId, correlationId);

        emit SyntheticTokenRenounced(tokenId, msg.sender, data.parentTokenId, data.groupId);
    }

    /**
     * @notice Revoke a specific synthetic subdomain token.
     * @dev Burns the subdomain token and makes it available for re-minting.
     *      Can only be called by the parent token owner.
     *      The subdomain must be revocable and not in a locked group.
     * @param tokenId The ID of the subdomain token to revoke.
     */
    function revokeSynthetic(uint256 tokenId) external {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        // Get token data
        SyntheticToken.SyntheticData memory data = s.syntheticToken.getSyntheticData(tokenId);

        // Verify this is a subdomain (not a root token)
        if (data.parentTokenId == 0) {
            revert LibProxyDomaRecord.NotSubdomain(tokenId);
        }

        // Only parent token owner can revoke
        LibProxyDomaRecord._verifyTokenOwnership(data.parentTokenId, msg.sender);

        // Verify token is revocable
        if (!data.revocable) {
            revert LibProxyDomaRecord.TokenNotRevocable(tokenId);
        }

        // Verify group not revoked
        if (s.syntheticToken.isGroupRevoked(data.parentTokenId, data.groupId)) {
            revert LibProxyDomaRecord.GroupRevoked(data.parentTokenId, data.groupId);
        }

        // Get token owner before burning
        address tokenOwner = s.syntheticToken.ownerOf(tokenId);

        // Generate correlation ID and burn
        string memory correlationId = LibProxyDomaRecord._useCorrelationId();
        s.syntheticToken.burnSynthetic(tokenId, correlationId);

        emit SyntheticTokenRevoked(
            tokenId,
            tokenOwner,
            msg.sender,
            data.parentTokenId,
            data.groupId
        );
    }

    /**
     * @notice Revoke a group of subdomains, preventing transfers and marking them for bulk operations.
     * @dev Can only be called by the parent token owner.
     *      Revokes all subdomain tokens in the specified group.
     *      Reduces the parent's subdomain count by the number of tokens in the group.
     * @param parentTokenId The ID of the parent synthetic token.
     * @param groupId The group identifier to revoke.
     */
    function revokeGroup(
        uint256 parentTokenId,
        uint256 groupId
    ) external onlyTokenOwner(parentTokenId) {
        LibProxyDomaRecord.ProxyDomaRecordStorage storage s = LibProxyDomaRecord
            .proxyDomaRecordStorage();

        // Verify this is a synthetic token (ownership tokens don't support subdomains)
        if (!LibProxyDomaRecord.isSyntheticToken(parentTokenId)) {
            revert LibProxyDomaRecord.TokenNotSynthetic(parentTokenId);
        }

        // Verify this is a root synthetic token (not a subdomain)
        uint256 grandparentTokenId = s.syntheticToken.getParentTokenId(parentTokenId);
        if (grandparentTokenId != 0) {
            revert InvalidTokenId(parentTokenId);
        }

        // Revoke the group (marks all tokens in group as revoked)
        s.syntheticToken.revokeGroup(parentTokenId, groupId);
    }

    /**
     * @dev Extract the last label from a host string (eg. "app" from "api.app").
     *      If no dot exists, returns the entire string.
     * @param host The full host string.
     * @return The last label after the final dot.
     */
    function _getLastLabel(string memory host) private pure returns (string memory) {
        bytes memory hostBytes = bytes(host);
        if (hostBytes.length == 0) {
            return host;
        }

        // Find the last occurrence of '.' by searching backwards
        for (uint256 i = hostBytes.length; i > 0; i--) {
            if (hostBytes[i - 1] == 0x2E) {
                // 0x2E is '.'
                // Found dot, extract substring after it
                bytes memory result = new bytes(hostBytes.length - i);
                for (uint256 j = 0; j < result.length; j++) {
                    result[j] = hostBytes[i + j];
                }
                return string(result);
            }
        }

        // No dot found, return the entire string
        return host;
    }
}

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

// solhint-disable-next-line interface-starts-with-i
interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

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

  function version() external view returns (uint256);

  function getRoundData(
    uint80 _roundId
  ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

  function latestRoundData()
    external
    view
    returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
    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);
        _;
    }

    function __AccessControl_init() internal onlyInitializing {
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControlUpgradeable).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 ",
                        StringsUpgradeable.toHexString(account),
                        " is missing role ",
                        StringsUpgradeable.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());
        }
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

// 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 IAccessControlUpgradeable {
    /**
     * @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;
}

File 5 of 60 : draft-IERC1822Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)

pragma solidity ^0.8.0;

/**
 * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
 * proxy whose upgrades are fully controlled by the current implementation.
 */
interface IERC1822ProxiableUpgradeable {
    /**
     * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
     * address.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy.
     */
    function proxiableUUID() external view returns (bytes32);
}

File 6 of 60 : IERC1967Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)

pragma solidity ^0.8.0;

/**
 * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
 *
 * _Available since v4.8.3._
 */
interface IERC1967Upgradeable {
    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Emitted when the beacon is changed.
     */
    event BeaconUpgraded(address indexed beacon);
}

File 7 of 60 : IBeaconUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeaconUpgradeable {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)

pragma solidity ^0.8.2;

import "../beacon/IBeaconUpgradeable.sol";
import "../../interfaces/IERC1967Upgradeable.sol";
import "../../interfaces/draft-IERC1822Upgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/StorageSlotUpgradeable.sol";
import {Initializable} from "../utils/Initializable.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 *
 * _Available since v4.1._
 */
abstract contract ERC1967UpgradeUpgradeable is Initializable, IERC1967Upgradeable {
    // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    function __ERC1967Upgrade_init() internal onlyInitializing {
    }

    function __ERC1967Upgrade_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Returns the current implementation address.
     */
    function _getImplementation() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Perform implementation upgrade
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    /**
     * @dev Perform implementation upgrade with additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
        _upgradeTo(newImplementation);
        if (data.length > 0 || forceCall) {
            AddressUpgradeable.functionDelegateCall(newImplementation, data);
        }
    }

    /**
     * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
        // Upgrades from old implementations will perform a rollback test. This test requires the new
        // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
        // this special case will break upgrade paths from old UUPS implementation to new ones.
        if (StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT).value) {
            _setImplementation(newImplementation);
        } else {
            try IERC1822ProxiableUpgradeable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
            } catch {
                revert("ERC1967Upgrade: new implementation is not UUPS");
            }
            _upgradeToAndCall(newImplementation, data, forceCall);
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Returns the current admin.
     */
    function _getAdmin() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        require(newAdmin != address(0), "ERC1967: new admin is the zero address");
        StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     */
    function _changeAdmin(address newAdmin) internal {
        emit AdminChanged(_getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
     */
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Returns the current beacon.
     */
    function _getBeacon() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
        require(
            AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
            "ERC1967: beacon implementation is not a contract"
        );
        StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    }

    /**
     * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
     * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
     *
     * Emits a {BeaconUpgraded} event.
     */
    function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);
        if (data.length > 0 || forceCall) {
            AddressUpgradeable.functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
        }
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

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

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}

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

pragma solidity ^0.8.0;

import "../../interfaces/draft-IERC1822Upgradeable.sol";
import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
import {Initializable} from "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 *
 * _Available since v4.1._
 */
abstract contract UUPSUpgradeable is Initializable, IERC1822ProxiableUpgradeable, ERC1967UpgradeUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
    address private immutable __self = address(this);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        require(address(this) != __self, "Function must be called through delegatecall");
        require(_getImplementation() == __self, "Function must be called through active proxy");
        _;
    }

    /**
     * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
     * callable on the implementing contract but not through proxies.
     */
    modifier notDelegated() {
        require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall");
        _;
    }

    function __UUPSUpgradeable_init() internal onlyInitializing {
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
     * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
     *
     * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
     * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
     * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
     */
    function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
        return _IMPLEMENTATION_SLOT;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeTo(address newImplementation) public virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     *
     * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallUUPS(newImplementation, data, true);
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeTo} and {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal override onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

File 11 of 60 : ERC721Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.0;

import "./IERC721Upgradeable.sol";
import "./IERC721ReceiverUpgradeable.sol";
import "./extensions/IERC721MetadataUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../utils/StringsUpgradeable.sol";
import "../../utils/introspection/ERC165Upgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.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 ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {
    using AddressUpgradeable for address;
    using StringsUpgradeable 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.
     */
    function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {
        __ERC721_init_unchained(name_, symbol_);
    }

    function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {
        return
            interfaceId == type(IERC721Upgradeable).interfaceId ||
            interfaceId == type(IERC721MetadataUpgradeable).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 = ERC721Upgradeable.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 = ERC721Upgradeable.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 = ERC721Upgradeable.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId, 1);

        // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
        owner = ERC721Upgradeable.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(ERC721Upgradeable.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(ERC721Upgradeable.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(ERC721Upgradeable.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 IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721ReceiverUpgradeable.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;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[44] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol)

pragma solidity ^0.8.0;

import "../ERC721Upgradeable.sol";
import "../../../utils/ContextUpgradeable.sol";
import {Initializable} from "../../../proxy/utils/Initializable.sol";

/**
 * @title ERC721 Burnable Token
 * @dev ERC721 Token that can be burned (destroyed).
 */
abstract contract ERC721BurnableUpgradeable is Initializable, ContextUpgradeable, ERC721Upgradeable {
    function __ERC721Burnable_init() internal onlyInitializing {
    }

    function __ERC721Burnable_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Burns `tokenId`. See {ERC721-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 tokenId) public virtual {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _burn(tokenId);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

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

pragma solidity ^0.8.0;

import "../IERC721Upgradeable.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721MetadataUpgradeable is IERC721Upgradeable {
    /**
     * @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);
}

File 14 of 60 : IERC721ReceiverUpgradeable.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 IERC721ReceiverUpgradeable {
    /**
     * @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);
}

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

pragma solidity ^0.8.0;

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

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721Upgradeable is IERC165Upgradeable {
    /**
     * @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);
}

// 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 AddressUpgradeable {
    /**
     * @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);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @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 ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    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;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165Upgradeable.sol";
import {Initializable} from "../../proxy/utils/Initializable.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 ERC165Upgradeable is Initializable, IERC165Upgradeable {
    function __ERC165_init() internal onlyInitializing {
    }

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165Upgradeable).interfaceId;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

// 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 IERC165Upgradeable {
    /**
     * @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);
}

// 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 MathUpgradeable {
    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);
        }
    }
}

// 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 SignedMathUpgradeable {
    /**
     * @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);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
 * _Available since v4.9 for `string`, `bytes`._
 */
library StorageSlotUpgradeable {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        /// @solidity memory-safe-assembly
        assembly {
            r.slot := store.slot
        }
    }
}

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

pragma solidity ^0.8.0;

import "./math/MathUpgradeable.sol";
import "./math/SignedMathUpgradeable.sol";

/**
 * @dev String operations.
 */
library StringsUpgradeable {
    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 = MathUpgradeable.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(SignedMathUpgradeable.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, MathUpgradeable.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));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reinitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
     *
     * NOTE: Consider following the ERC-7201 formula to derive storage locations.
     */
    function _initializableStorageSlot() internal pure virtual returns (bytes32) {
        return INITIALIZABLE_STORAGE;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        bytes32 slot = _initializableStorageSlot();
        assembly {
            $.slot := slot
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.20;

import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: The upgradeable version of this contract does not use an immutable cache and recomputes the domain separator
 * each time {_domainSeparatorV4} is called. That is cheaper than accessing a cached version in cold storage.
 */
abstract contract EIP712Upgradeable is Initializable, IERC5267 {
    bytes32 private constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    /// @custom:storage-location erc7201:openzeppelin.storage.EIP712
    struct EIP712Storage {
        /// @custom:oz-renamed-from _HASHED_NAME
        bytes32 _hashedName;
        /// @custom:oz-renamed-from _HASHED_VERSION
        bytes32 _hashedVersion;

        string _name;
        string _version;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;

    function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
        assembly {
            $.slot := EIP712StorageLocation
        }
    }

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
        __EIP712_init_unchained(name, version);
    }

    function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
        EIP712Storage storage $ = _getEIP712Storage();
        $._name = name;
        $._version = version;

        // Reset prior values in storage if upgrading
        $._hashedName = 0;
        $._hashedVersion = 0;
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        return _buildDomainSeparator();
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /// @inheritdoc IERC5267
    function eip712Domain()
        public
        view
        virtual
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        EIP712Storage storage $ = _getEIP712Storage();
        // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
        // and the EIP712 domain is not reliable, as it will be missing name and version.
        require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");

        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
     * are a concern.
     */
    function _EIP712Name() internal view virtual returns (string memory) {
        EIP712Storage storage $ = _getEIP712Storage();
        return $._name;
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
     * are a concern.
     */
    function _EIP712Version() internal view virtual returns (string memory) {
        EIP712Storage storage $ = _getEIP712Storage();
        return $._version;
    }

    /**
     * @dev The hash of the name parameter for the EIP712 domain.
     *
     * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
     */
    function _EIP712NameHash() internal view returns (bytes32) {
        EIP712Storage storage $ = _getEIP712Storage();
        string memory name = _EIP712Name();
        if (bytes(name).length > 0) {
            return keccak256(bytes(name));
        } else {
            // If the name is empty, the contract may have been upgraded without initializing the new storage.
            // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
            bytes32 hashedName = $._hashedName;
            if (hashedName != 0) {
                return hashedName;
            } else {
                return keccak256("");
            }
        }
    }

    /**
     * @dev The hash of the version parameter for the EIP712 domain.
     *
     * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
     */
    function _EIP712VersionHash() internal view returns (bytes32) {
        EIP712Storage storage $ = _getEIP712Storage();
        string memory version = _EIP712Version();
        if (bytes(version).length > 0) {
            return keccak256(bytes(version));
        } else {
            // If the version is empty, the contract may have been upgraded without initializing the new storage.
            // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
            bytes32 hashedVersion = $._hashedVersion;
            if (hashedVersion != 0) {
                return hashedVersion;
            } else {
                return keccak256("");
            }
        }
    }
}

File 26 of 60 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

import {IERC165} from "../utils/introspection/IERC165.sol";

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC2981.sol)

pragma solidity >=0.6.2;

import {IERC165} from "../utils/introspection/IERC165.sol";

/**
 * @dev Interface for the NFT Royalty Standard.
 *
 * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
 * support for royalty payments across all NFT marketplaces and ecosystem participants.
 */
interface IERC2981 is IERC165 {
    /**
     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
     *
     * NOTE: ERC-2981 allows setting the royalty to 100% of the price. In that case all the price would be sent to the
     * royalty receiver and 0 tokens to the seller. Contracts dealing with royalty should consider empty transfers.
     */
    function royaltyInfo(
        uint256 tokenId,
        uint256 salePrice
    ) external view returns (address receiver, uint256 royaltyAmount);
}

File 28 of 60 : IERC5267.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC5267.sol)

pragma solidity >=0.4.16;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}

File 29 of 60 : Arrays.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Arrays.sol)
// This file was procedurally generated from scripts/generate/templates/Arrays.js.

pragma solidity ^0.8.20;

import {Comparators} from "./Comparators.sol";
import {SlotDerivation} from "./SlotDerivation.sol";
import {StorageSlot} from "./StorageSlot.sol";
import {Math} from "./math/Math.sol";

/**
 * @dev Collection of functions related to array types.
 */
library Arrays {
    using SlotDerivation for bytes32;
    using StorageSlot for bytes32;

    /**
     * @dev Sort an array of uint256 (in memory) following the provided comparator function.
     *
     * This function does the sorting "in place", meaning that it overrides the input. The object is returned for
     * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
     *
     * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
     * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
     * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
     * consume more gas than is available in a block, leading to potential DoS.
     *
     * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
     */
    function sort(
        uint256[] memory array,
        function(uint256, uint256) pure returns (bool) comp
    ) internal pure returns (uint256[] memory) {
        _quickSort(_begin(array), _end(array), comp);
        return array;
    }

    /**
     * @dev Variant of {sort} that sorts an array of uint256 in increasing order.
     */
    function sort(uint256[] memory array) internal pure returns (uint256[] memory) {
        sort(array, Comparators.lt);
        return array;
    }

    /**
     * @dev Sort an array of address (in memory) following the provided comparator function.
     *
     * This function does the sorting "in place", meaning that it overrides the input. The object is returned for
     * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
     *
     * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
     * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
     * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
     * consume more gas than is available in a block, leading to potential DoS.
     *
     * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
     */
    function sort(
        address[] memory array,
        function(address, address) pure returns (bool) comp
    ) internal pure returns (address[] memory) {
        sort(_castToUint256Array(array), _castToUint256Comp(comp));
        return array;
    }

    /**
     * @dev Variant of {sort} that sorts an array of address in increasing order.
     */
    function sort(address[] memory array) internal pure returns (address[] memory) {
        sort(_castToUint256Array(array), Comparators.lt);
        return array;
    }

    /**
     * @dev Sort an array of bytes32 (in memory) following the provided comparator function.
     *
     * This function does the sorting "in place", meaning that it overrides the input. The object is returned for
     * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array.
     *
     * NOTE: this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the
     * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful
     * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may
     * consume more gas than is available in a block, leading to potential DoS.
     *
     * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way.
     */
    function sort(
        bytes32[] memory array,
        function(bytes32, bytes32) pure returns (bool) comp
    ) internal pure returns (bytes32[] memory) {
        sort(_castToUint256Array(array), _castToUint256Comp(comp));
        return array;
    }

    /**
     * @dev Variant of {sort} that sorts an array of bytes32 in increasing order.
     */
    function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) {
        sort(_castToUint256Array(array), Comparators.lt);
        return array;
    }

    /**
     * @dev Performs a quick sort of a segment of memory. The segment sorted starts at `begin` (inclusive), and stops
     * at end (exclusive). Sorting follows the `comp` comparator.
     *
     * Invariant: `begin <= end`. This is the case when initially called by {sort} and is preserved in subcalls.
     *
     * IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should
     * be used only if the limits are within a memory array.
     */
    function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure {
        unchecked {
            if (end - begin < 0x40) return;

            // Use first element as pivot
            uint256 pivot = _mload(begin);
            // Position where the pivot should be at the end of the loop
            uint256 pos = begin;

            for (uint256 it = begin + 0x20; it < end; it += 0x20) {
                if (comp(_mload(it), pivot)) {
                    // If the value stored at the iterator's position comes before the pivot, we increment the
                    // position of the pivot and move the value there.
                    pos += 0x20;
                    _swap(pos, it);
                }
            }

            _swap(begin, pos); // Swap pivot into place
            _quickSort(begin, pos, comp); // Sort the left side of the pivot
            _quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot
        }
    }

    /**
     * @dev Pointer to the memory location of the first element of `array`.
     */
    function _begin(uint256[] memory array) private pure returns (uint256 ptr) {
        assembly ("memory-safe") {
            ptr := add(array, 0x20)
        }
    }

    /**
     * @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word
     * that comes just after the last element of the array.
     */
    function _end(uint256[] memory array) private pure returns (uint256 ptr) {
        unchecked {
            return _begin(array) + array.length * 0x20;
        }
    }

    /**
     * @dev Load memory word (as a uint256) at location `ptr`.
     */
    function _mload(uint256 ptr) private pure returns (uint256 value) {
        assembly {
            value := mload(ptr)
        }
    }

    /**
     * @dev Swaps the elements memory location `ptr1` and `ptr2`.
     */
    function _swap(uint256 ptr1, uint256 ptr2) private pure {
        assembly {
            let value1 := mload(ptr1)
            let value2 := mload(ptr2)
            mstore(ptr1, value2)
            mstore(ptr2, value1)
        }
    }

    /// @dev Helper: low level cast address memory array to uint256 memory array
    function _castToUint256Array(address[] memory input) private pure returns (uint256[] memory output) {
        assembly {
            output := input
        }
    }

    /// @dev Helper: low level cast bytes32 memory array to uint256 memory array
    function _castToUint256Array(bytes32[] memory input) private pure returns (uint256[] memory output) {
        assembly {
            output := input
        }
    }

    /// @dev Helper: low level cast address comp function to uint256 comp function
    function _castToUint256Comp(
        function(address, address) pure returns (bool) input
    ) private pure returns (function(uint256, uint256) pure returns (bool) output) {
        assembly {
            output := input
        }
    }

    /// @dev Helper: low level cast bytes32 comp function to uint256 comp function
    function _castToUint256Comp(
        function(bytes32, bytes32) pure returns (bool) input
    ) private pure returns (function(uint256, uint256) pure returns (bool) output) {
        assembly {
            output := input
        }
    }

    /**
     * @dev Searches a sorted `array` and returns the first index that contains
     * a value greater or equal to `element`. If no such index exists (i.e. all
     * values in the array are strictly less than `element`), the array length is
     * returned. Time complexity O(log n).
     *
     * NOTE: The `array` is expected to be sorted in ascending order, and to
     * contain no repeated elements.
     *
     * IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks
     * support for repeated elements in the array. The {lowerBound} function should
     * be used instead.
     */
    function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
        uint256 low = 0;
        uint256 high = array.length;

        if (high == 0) {
            return 0;
        }

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds towards zero (it does integer division with truncation).
            if (unsafeAccess(array, mid).value > element) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
        if (low > 0 && unsafeAccess(array, low - 1).value == element) {
            return low - 1;
        } else {
            return low;
        }
    }

    /**
     * @dev Searches an `array` sorted in ascending order and returns the first
     * index that contains a value greater or equal than `element`. If no such index
     * exists (i.e. all values in the array are strictly less than `element`), the array
     * length is returned. Time complexity O(log n).
     *
     * See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound].
     */
    function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
        uint256 low = 0;
        uint256 high = array.length;

        if (high == 0) {
            return 0;
        }

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds towards zero (it does integer division with truncation).
            if (unsafeAccess(array, mid).value < element) {
                // this cannot overflow because mid < high
                unchecked {
                    low = mid + 1;
                }
            } else {
                high = mid;
            }
        }

        return low;
    }

    /**
     * @dev Searches an `array` sorted in ascending order and returns the first
     * index that contains a value strictly greater than `element`. If no such index
     * exists (i.e. all values in the array are strictly less than `element`), the array
     * length is returned. Time complexity O(log n).
     *
     * See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound].
     */
    function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) {
        uint256 low = 0;
        uint256 high = array.length;

        if (high == 0) {
            return 0;
        }

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds towards zero (it does integer division with truncation).
            if (unsafeAccess(array, mid).value > element) {
                high = mid;
            } else {
                // this cannot overflow because mid < high
                unchecked {
                    low = mid + 1;
                }
            }
        }

        return low;
    }

    /**
     * @dev Same as {lowerBound}, but with an array in memory.
     */
    function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
        uint256 low = 0;
        uint256 high = array.length;

        if (high == 0) {
            return 0;
        }

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds towards zero (it does integer division with truncation).
            if (unsafeMemoryAccess(array, mid) < element) {
                // this cannot overflow because mid < high
                unchecked {
                    low = mid + 1;
                }
            } else {
                high = mid;
            }
        }

        return low;
    }

    /**
     * @dev Same as {upperBound}, but with an array in memory.
     */
    function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) {
        uint256 low = 0;
        uint256 high = array.length;

        if (high == 0) {
            return 0;
        }

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds towards zero (it does integer division with truncation).
            if (unsafeMemoryAccess(array, mid) > element) {
                high = mid;
            } else {
                // this cannot overflow because mid < high
                unchecked {
                    low = mid + 1;
                }
            }
        }

        return low;
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) {
        bytes32 slot;
        assembly ("memory-safe") {
            slot := arr.slot
        }
        return slot.deriveArray().offset(pos).getAddressSlot();
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) {
        bytes32 slot;
        assembly ("memory-safe") {
            slot := arr.slot
        }
        return slot.deriveArray().offset(pos).getBytes32Slot();
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) {
        bytes32 slot;
        assembly ("memory-safe") {
            slot := arr.slot
        }
        return slot.deriveArray().offset(pos).getUint256Slot();
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeAccess(bytes[] storage arr, uint256 pos) internal pure returns (StorageSlot.BytesSlot storage) {
        bytes32 slot;
        assembly ("memory-safe") {
            slot := arr.slot
        }
        return slot.deriveArray().offset(pos).getBytesSlot();
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeAccess(string[] storage arr, uint256 pos) internal pure returns (StorageSlot.StringSlot storage) {
        bytes32 slot;
        assembly ("memory-safe") {
            slot := arr.slot
        }
        return slot.deriveArray().offset(pos).getStringSlot();
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) {
        assembly {
            res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
        }
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeMemoryAccess(bytes32[] memory arr, uint256 pos) internal pure returns (bytes32 res) {
        assembly {
            res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
        }
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) {
        assembly {
            res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
        }
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeMemoryAccess(bytes[] memory arr, uint256 pos) internal pure returns (bytes memory res) {
        assembly {
            res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
        }
    }

    /**
     * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
     *
     * WARNING: Only use if you are certain `pos` is lower than the array length.
     */
    function unsafeMemoryAccess(string[] memory arr, uint256 pos) internal pure returns (string memory res) {
        assembly {
            res := mload(add(add(arr, 0x20), mul(pos, 0x20)))
        }
    }

    /**
     * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
     *
     * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
     */
    function unsafeSetLength(address[] storage array, uint256 len) internal {
        assembly ("memory-safe") {
            sstore(array.slot, len)
        }
    }

    /**
     * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
     *
     * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
     */
    function unsafeSetLength(bytes32[] storage array, uint256 len) internal {
        assembly ("memory-safe") {
            sstore(array.slot, len)
        }
    }

    /**
     * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
     *
     * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
     */
    function unsafeSetLength(uint256[] storage array, uint256 len) internal {
        assembly ("memory-safe") {
            sstore(array.slot, len)
        }
    }

    /**
     * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
     *
     * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
     */
    function unsafeSetLength(bytes[] storage array, uint256 len) internal {
        assembly ("memory-safe") {
            sstore(array.slot, len)
        }
    }

    /**
     * @dev Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden.
     *
     * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased.
     */
    function unsafeSetLength(string[] storage array, uint256 len) internal {
        assembly ("memory-safe") {
            sstore(array.slot, len)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides a set of functions to compare values.
 *
 * _Available since v5.1._
 */
library Comparators {
    function lt(uint256 a, uint256 b) internal pure returns (bool) {
        return a < b;
    }

    function gt(uint256 a, uint256 b) internal pure returns (bool) {
        return a > b;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(
        bytes32 hash,
        bytes memory signature
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly ("memory-safe") {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an ERC-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        assembly ("memory-safe") {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an ERC-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an ERC-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
     */
    function toDataWithIntendedValidatorHash(
        address validator,
        bytes32 messageHash
    ) internal pure returns (bytes32 digest) {
        assembly ("memory-safe") {
            mstore(0x00, hex"19_00")
            mstore(0x02, shl(96, validator))
            mstore(0x16, messageHash)
            digest := keccak256(0x00, 0x36)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * 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[ERC 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 34 of 60 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Return the 512-bit addition of two uint256.
     *
     * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
     */
    function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        assembly ("memory-safe") {
            low := add(a, b)
            high := lt(low, a)
        }
    }

    /**
     * @dev Return the 512-bit multiplication of two uint256.
     *
     * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
     */
    function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
        // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
        // variables such that product = high * 2²⁵⁶ + low.
        assembly ("memory-safe") {
            let mm := mulmod(a, b, not(0))
            low := mul(a, b)
            high := sub(sub(mm, low), lt(mm, low))
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a + b;
            success = c >= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a - b;
            success = c <= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a * b;
            assembly ("memory-safe") {
                // Only true when the multiplication doesn't overflow
                // (c / a == b) || (a == 0)
                success := or(eq(div(c, a), b), iszero(a))
            }
            // equivalent to: success ? c : 0
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `DIV` opcode returns zero when the denominator is 0.
                result := div(a, b)
            }
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `MOD` opcode returns zero when the denominator is 0.
                result := mod(a, b)
            }
        }
    }

    /**
     * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryAdd(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
     */
    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
        (, uint256 result) = trySub(a, b);
        return result;
    }

    /**
     * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryMul(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * SafeCast.toUint(condition));
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(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 towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }

        // The following calculation ensures accurate ceiling division without overflow.
        // Since a is non-zero, (a - 1) / b will not overflow.
        // The largest possible result occurs when (a - 1) / b is type(uint256).max,
        // but the largest value we can obtain is type(uint256).max - 1, which happens
        // when a = type(uint256).max and b = 1.
        unchecked {
            return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
        }
    }

    /**
     * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     *
     * 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 {
            (uint256 high, uint256 low) = mul512(x, y);

            // Handle non-overflow cases, 256 by 256 division.
            if (high == 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 low / denominator;
            }

            // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
            if (denominator <= high) {
                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [high low].
            uint256 remainder;
            assembly ("memory-safe") {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                high := sub(high, gt(remainder, low))
                low := sub(low, 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.

            uint256 twos = denominator & (0 - denominator);
            assembly ("memory-safe") {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [high low] by twos.
                low := div(low, twos)

                // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from high into low.
            low |= high * twos;

            // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
            // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
            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⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
            inverse *= 2 - denominator * inverse; // inverse mod 2³²
            inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
            inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶

            // 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²⁵⁶. Since the preconditions guarantee that the outcome is
            // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
            // is no longer required.
            result = low * inverse;
            return result;
        }
    }

    /**
     * @dev 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) {
        return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    }

    /**
     * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
     */
    function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);
            if (high >= 1 << n) {
                Panic.panic(Panic.UNDER_OVERFLOW);
            }
            return (high << (256 - n)) | (low >> n);
        }
    }

    /**
     * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
     */
    function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
        return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
    }

    /**
     * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
     *
     * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
     * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
     *
     * If the input value is not inversible, 0 is returned.
     *
     * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
     * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
     */
    function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
        unchecked {
            if (n == 0) return 0;

            // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
            // Used to compute integers x and y such that: ax + ny = gcd(a, n).
            // When the gcd is 1, then the inverse of a modulo n exists and it's x.
            // ax + ny = 1
            // ax = 1 + (-y)n
            // ax ≡ 1 (mod n) # x is the inverse of a modulo n

            // If the remainder is 0 the gcd is n right away.
            uint256 remainder = a % n;
            uint256 gcd = n;

            // Therefore the initial coefficients are:
            // ax + ny = gcd(a, n) = n
            // 0a + 1n = n
            int256 x = 0;
            int256 y = 1;

            while (remainder != 0) {
                uint256 quotient = gcd / remainder;

                (gcd, remainder) = (
                    // The old remainder is the next gcd to try.
                    remainder,
                    // Compute the next remainder.
                    // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
                    // where gcd is at most n (capped to type(uint256).max)
                    gcd - remainder * quotient
                );

                (x, y) = (
                    // Increment the coefficient of a.
                    y,
                    // Decrement the coefficient of n.
                    // Can overflow, but the result is casted to uint256 so that the
                    // next value of y is "wrapped around" to a value between 0 and n - 1.
                    x - y * int256(quotient)
                );
            }

            if (gcd != 1) return 0; // No inverse exists.
            return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
        }
    }

    /**
     * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
     *
     * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
     * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
     * `a**(p-2)` is the modular multiplicative inverse of a in Fp.
     *
     * NOTE: this function does NOT check that `p` is a prime greater than `2`.
     */
    function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
        unchecked {
            return Math.modExp(a, p - 2, p);
        }
    }

    /**
     * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
     *
     * Requirements:
     * - modulus can't be zero
     * - underlying staticcall to precompile must succeed
     *
     * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
     * sure the chain you're using it on supports the precompiled contract for modular exponentiation
     * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
     * the underlying function will succeed given the lack of a revert, but the result may be incorrectly
     * interpreted as 0.
     */
    function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
        (bool success, uint256 result) = tryModExp(b, e, m);
        if (!success) {
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }
        return result;
    }

    /**
     * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
     * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
     * to operate modulo 0 or if the underlying precompile reverted.
     *
     * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
     * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
     * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
     * of a revert, but the result may be incorrectly interpreted as 0.
     */
    function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
        if (m == 0) return (false, 0);
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            // | Offset    | Content    | Content (Hex)                                                      |
            // |-----------|------------|--------------------------------------------------------------------|
            // | 0x00:0x1f | size of b  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x20:0x3f | size of e  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x40:0x5f | size of m  | 0x0000000000000000000000000000000000000000000000000000000000000020 |
            // | 0x60:0x7f | value of b | 0x<.............................................................b> |
            // | 0x80:0x9f | value of e | 0x<.............................................................e> |
            // | 0xa0:0xbf | value of m | 0x<.............................................................m> |
            mstore(ptr, 0x20)
            mstore(add(ptr, 0x20), 0x20)
            mstore(add(ptr, 0x40), 0x20)
            mstore(add(ptr, 0x60), b)
            mstore(add(ptr, 0x80), e)
            mstore(add(ptr, 0xa0), m)

            // Given the result < m, it's guaranteed to fit in 32 bytes,
            // so we can use the memory scratch space located at offset 0.
            success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
            result := mload(0x00)
        }
    }

    /**
     * @dev Variant of {modExp} that supports inputs of arbitrary length.
     */
    function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
        (bool success, bytes memory result) = tryModExp(b, e, m);
        if (!success) {
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }
        return result;
    }

    /**
     * @dev Variant of {tryModExp} that supports inputs of arbitrary length.
     */
    function tryModExp(
        bytes memory b,
        bytes memory e,
        bytes memory m
    ) internal view returns (bool success, bytes memory result) {
        if (_zeroBytes(m)) return (false, new bytes(0));

        uint256 mLen = m.length;

        // Encode call args in result and move the free memory pointer
        result = abi.encodePacked(b.length, e.length, mLen, b, e, m);

        assembly ("memory-safe") {
            let dataPtr := add(result, 0x20)
            // Write result on top of args to avoid allocating extra memory.
            success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
            // Overwrite the length.
            // result.length > returndatasize() is guaranteed because returndatasize() == m.length
            mstore(result, mLen)
            // Set the memory pointer after the returned data.
            mstore(0x40, add(dataPtr, mLen))
        }
    }

    /**
     * @dev Returns whether the provided byte array is zero.
     */
    function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
        for (uint256 i = 0; i < byteArray.length; ++i) {
            if (byteArray[i] != 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * This method is based on Newton's method for computing square roots; the algorithm is restricted to only
     * using integer operations.
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        unchecked {
            // Take care of easy edge cases when a == 0 or a == 1
            if (a <= 1) {
                return a;
            }

            // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
            // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
            // the current value as `ε_n = | x_n - sqrt(a) |`.
            //
            // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
            // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
            // bigger than any uint256.
            //
            // By noticing that
            // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
            // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
            // to the msb function.
            uint256 aa = a;
            uint256 xn = 1;

            if (aa >= (1 << 128)) {
                aa >>= 128;
                xn <<= 64;
            }
            if (aa >= (1 << 64)) {
                aa >>= 64;
                xn <<= 32;
            }
            if (aa >= (1 << 32)) {
                aa >>= 32;
                xn <<= 16;
            }
            if (aa >= (1 << 16)) {
                aa >>= 16;
                xn <<= 8;
            }
            if (aa >= (1 << 8)) {
                aa >>= 8;
                xn <<= 4;
            }
            if (aa >= (1 << 4)) {
                aa >>= 4;
                xn <<= 2;
            }
            if (aa >= (1 << 2)) {
                xn <<= 1;
            }

            // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
            //
            // We can refine our estimation by noticing that the middle of that interval minimizes the error.
            // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
            // This is going to be our x_0 (and ε_0)
            xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)

            // From here, Newton's method give us:
            // x_{n+1} = (x_n + a / x_n) / 2
            //
            // One should note that:
            // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
            //              = ((x_n² + a) / (2 * x_n))² - a
            //              = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
            //              = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
            //              = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
            //              = (x_n² - a)² / (2 * x_n)²
            //              = ((x_n² - a) / (2 * x_n))²
            //              ≥ 0
            // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
            //
            // This gives us the proof of quadratic convergence of the sequence:
            // ε_{n+1} = | x_{n+1} - sqrt(a) |
            //         = | (x_n + a / x_n) / 2 - sqrt(a) |
            //         = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
            //         = | (x_n - sqrt(a))² / (2 * x_n) |
            //         = | ε_n² / (2 * x_n) |
            //         = ε_n² / | (2 * x_n) |
            //
            // For the first iteration, we have a special case where x_0 is known:
            // ε_1 = ε_0² / | (2 * x_0) |
            //     ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
            //     ≤ 2**(2*e-4) / (3 * 2**(e-1))
            //     ≤ 2**(e-3) / 3
            //     ≤ 2**(e-3-log2(3))
            //     ≤ 2**(e-4.5)
            //
            // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
            // ε_{n+1} = ε_n² / | (2 * x_n) |
            //         ≤ (2**(e-k))² / (2 * 2**(e-1))
            //         ≤ 2**(2*e-2*k) / 2**e
            //         ≤ 2**(e-2*k)
            xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5)  -- special case, see above
            xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9)    -- general case with k = 4.5
            xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18)   -- general case with k = 9
            xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36)   -- general case with k = 18
            xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72)   -- general case with k = 36
            xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144)  -- general case with k = 72

            // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
            // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
            // sqrt(a) or sqrt(a) + 1.
            return xn - SafeCast.toUint(xn > a / xn);
        }
    }

    /**
     * @dev 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 + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 x) internal pure returns (uint256 r) {
        // If value has upper 128 bits set, log2 result is at least 128
        r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
        // If upper 64 bits of 128-bit half set, add 64 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
        // If upper 32 bits of 64-bit half set, add 32 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
        // If upper 16 bits of 32-bit half set, add 16 to result
        r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
        // If upper 8 bits of 16-bit half set, add 8 to result
        r |= SafeCast.toUint((x >> r) > 0xff) << 3;
        // If upper 4 bits of 8-bit half set, add 4 to result
        r |= SafeCast.toUint((x >> r) > 0xf) << 2;

        // Shifts value right by the current result and use it as an index into this lookup table:
        //
        // | x (4 bits) |  index  | table[index] = MSB position |
        // |------------|---------|-----------------------------|
        // |    0000    |    0    |        table[0] = 0         |
        // |    0001    |    1    |        table[1] = 0         |
        // |    0010    |    2    |        table[2] = 1         |
        // |    0011    |    3    |        table[3] = 1         |
        // |    0100    |    4    |        table[4] = 2         |
        // |    0101    |    5    |        table[5] = 2         |
        // |    0110    |    6    |        table[6] = 2         |
        // |    0111    |    7    |        table[7] = 2         |
        // |    1000    |    8    |        table[8] = 3         |
        // |    1001    |    9    |        table[9] = 3         |
        // |    1010    |   10    |        table[10] = 3        |
        // |    1011    |   11    |        table[11] = 3        |
        // |    1100    |   12    |        table[12] = 3        |
        // |    1101    |   13    |        table[13] = 3        |
        // |    1110    |   14    |        table[14] = 3        |
        // |    1111    |   15    |        table[15] = 3        |
        //
        // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
        assembly ("memory-safe") {
            r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
        }
    }

    /**
     * @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 + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * 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 + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * 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 x) internal pure returns (uint256 r) {
        // If value has upper 128 bits set, log2 result is at least 128
        r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
        // If upper 64 bits of 128-bit half set, add 64 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
        // If upper 32 bits of 64-bit half set, add 32 to result
        r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
        // If upper 16 bits of 32-bit half set, add 16 to result
        r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
        // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
        return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
    }

    /**
     * @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 + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

File 35 of 60 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }

    /**
     * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
     */
    function toUint(bool b) internal pure returns (uint256 u) {
        assembly ("memory-safe") {
            u := iszero(iszero(b))
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));
        }
    }

    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return ternary(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 {
            // Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson.
            // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,
            // taking advantage of the most significant (or "sign" bit) in two's complement representation.
            // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,
            // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).
            int256 mask = n >> 255;

            // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.
            return uint256((n + mask) ^ mask);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)

pragma solidity ^0.8.20;

/**
 * @dev Helper library for emitting standardized panic codes.
 *
 * ```solidity
 * contract Example {
 *      using Panic for uint256;
 *
 *      // Use any of the declared internal constants
 *      function foo() { Panic.GENERIC.panic(); }
 *
 *      // Alternatively
 *      function foo() { Panic.panic(Panic.GENERIC); }
 * }
 * ```
 *
 * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
 *
 * _Available since v5.1._
 */
// slither-disable-next-line unused-state
library Panic {
    /// @dev generic / unspecified error
    uint256 internal constant GENERIC = 0x00;
    /// @dev used by the assert() builtin
    uint256 internal constant ASSERT = 0x01;
    /// @dev arithmetic underflow or overflow
    uint256 internal constant UNDER_OVERFLOW = 0x11;
    /// @dev division or modulo by zero
    uint256 internal constant DIVISION_BY_ZERO = 0x12;
    /// @dev enum conversion error
    uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
    /// @dev invalid encoding in storage
    uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
    /// @dev empty array pop
    uint256 internal constant EMPTY_ARRAY_POP = 0x31;
    /// @dev array out of bounds access
    uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
    /// @dev resource error (too large allocation or too large array)
    uint256 internal constant RESOURCE_ERROR = 0x41;
    /// @dev calling invalid internal function
    uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;

    /// @dev Reverts with a panic code. Recommended to use with
    /// the internal constants with predefined codes.
    function panic(uint256 code) internal pure {
        assembly ("memory-safe") {
            mstore(0x00, 0x4e487b71)
            mstore(0x20, code)
            revert(0x1c, 0x24)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/SlotDerivation.sol)
// This file was procedurally generated from scripts/generate/templates/SlotDerivation.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
 * corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
 * the solidity language / compiler.
 *
 * See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
 *
 * Example usage:
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using StorageSlot for bytes32;
 *     using SlotDerivation for bytes32;
 *
 *     // Declare a namespace
 *     string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot
 *
 *     function setValueInNamespace(uint256 key, address newValue) internal {
 *         _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
 *     }
 *
 *     function getValueInNamespace(uint256 key) internal view returns (address) {
 *         return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {StorageSlot}.
 *
 * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
 * upgrade safety will ignore the slots accessed through this library.
 *
 * _Available since v5.1._
 */
library SlotDerivation {
    /**
     * @dev Derive an ERC-7201 slot from a string (namespace).
     */
    function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
        assembly ("memory-safe") {
            mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
            slot := and(keccak256(0x00, 0x20), not(0xff))
        }
    }

    /**
     * @dev Add an offset to a slot to get the n-th element of a structure or an array.
     */
    function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
        unchecked {
            return bytes32(uint256(slot) + pos);
        }
    }

    /**
     * @dev Derive the location of the first element in an array from the slot where the length is stored.
     */
    function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            mstore(0x00, slot)
            result := keccak256(0x00, 0x20)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            mstore(0x00, and(key, shr(96, not(0))))
            mstore(0x20, slot)
            result := keccak256(0x00, 0x40)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            mstore(0x00, iszero(iszero(key)))
            mstore(0x20, slot)
            result := keccak256(0x00, 0x40)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            mstore(0x00, key)
            mstore(0x20, slot)
            result := keccak256(0x00, 0x40)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            mstore(0x00, key)
            mstore(0x20, slot)
            result := keccak256(0x00, 0x40)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            mstore(0x00, key)
            mstore(0x20, slot)
            result := keccak256(0x00, 0x40)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            let length := mload(key)
            let begin := add(key, 0x20)
            let end := add(begin, length)
            let cache := mload(end)
            mstore(end, slot)
            result := keccak256(begin, add(length, 0x20))
            mstore(end, cache)
        }
    }

    /**
     * @dev Derive the location of a mapping element from the key.
     */
    function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) {
        assembly ("memory-safe") {
            let length := mload(key)
            let begin := add(key, 0x20)
            let end := add(begin, length)
            let cache := mload(end)
            mstore(end, slot)
            result := keccak256(begin, add(length, 0x20))
            mstore(end, cache)
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    using SafeCast for *;

    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;
    uint256 private constant SPECIAL_CHARS_LOOKUP =
        (1 << 0x08) | // backspace
            (1 << 0x09) | // tab
            (1 << 0x0a) | // newline
            (1 << 0x0c) | // form feed
            (1 << 0x0d) | // carriage return
            (1 << 0x22) | // double quote
            (1 << 0x5c); // backslash

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev The string being parsed contains characters that are not in scope of the given base.
     */
    error StringsInvalidChar();

    /**
     * @dev The string being parsed is not a properly formatted address.
     */
    error StringsInvalidAddressFormat();

    /**
     * @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;
            assembly ("memory-safe") {
                ptr := add(add(buffer, 0x20), length)
            }
            while (true) {
                ptr--;
                assembly ("memory-safe") {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(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) {
        uint256 localValue = value;
        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] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        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 Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
     * representation, according to EIP-55.
     */
    function toChecksumHexString(address addr) internal pure returns (string memory) {
        bytes memory buffer = bytes(toHexString(addr));

        // hash the hex part of buffer (skip length + 2 bytes, length 40)
        uint256 hashValue;
        assembly ("memory-safe") {
            hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
        }

        for (uint256 i = 41; i > 1; --i) {
            // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
            if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
                // case shift by xoring with 0x20
                buffer[i] ^= 0x20;
            }
            hashValue >>= 4;
        }
        return string(buffer);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }

    /**
     * @dev Parse a decimal string and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input) internal pure returns (uint256) {
        return parseUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        uint256 result = 0;
        for (uint256 i = begin; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 9) return (false, 0);
            result *= 10;
            result += chr;
        }
        return (true, result);
    }

    /**
     * @dev Parse a decimal string and returns the value as a `int256`.
     *
     * Requirements:
     * - The string must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input) internal pure returns (int256) {
        return parseInt(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
        (bool success, int256 value) = tryParseInt(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
     * the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
        return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
    }

    uint256 private constant ABS_MIN_INT256 = 2 ** 255;

    /**
     * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character or if the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, int256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseIntUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseIntUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, int256 value) {
        bytes memory buffer = bytes(input);

        // Check presence of a negative sign.
        bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        bool positiveSign = sign == bytes1("+");
        bool negativeSign = sign == bytes1("-");
        uint256 offset = (positiveSign || negativeSign).toUint();

        (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);

        if (absSuccess && absValue < ABS_MIN_INT256) {
            return (true, negativeSign ? -int256(absValue) : int256(absValue));
        } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
            return (true, type(int256).min);
        } else return (false, 0);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input) internal pure returns (uint256) {
        return parseHexUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseHexUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
     * invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseHexUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseHexUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        // skip 0x prefix if present
        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        uint256 offset = hasPrefix.toUint() * 2;

        uint256 result = 0;
        for (uint256 i = begin + offset; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 15) return (false, 0);
            result *= 16;
            unchecked {
                // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
                // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
                result += chr;
            }
        }
        return (true, result);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input) internal pure returns (address) {
        return parseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
        (bool success, address value) = tryParseAddress(input, begin, end);
        if (!success) revert StringsInvalidAddressFormat();
        return value;
    }

    /**
     * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
     * formatted address. See {parseAddress-string} requirements.
     */
    function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
        return tryParseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
     * formatted address. See {parseAddress-string-uint256-uint256} requirements.
     */
    function tryParseAddress(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, address value) {
        if (end > bytes(input).length || begin > end) return (false, address(0));

        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        uint256 expectedLength = 40 + hasPrefix.toUint() * 2;

        // check that input is the correct length
        if (end - begin == expectedLength) {
            // length guarantees that this does not overflow, and value is at most type(uint160).max
            (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
            return (s, address(uint160(v)));
        } else {
            return (false, address(0));
        }
    }

    function _tryParseChr(bytes1 chr) private pure returns (uint8) {
        uint8 value = uint8(chr);

        // Try to parse `chr`:
        // - Case 1: [0-9]
        // - Case 2: [a-f]
        // - Case 3: [A-F]
        // - otherwise not supported
        unchecked {
            if (value > 47 && value < 58) value -= 48;
            else if (value > 96 && value < 103) value -= 87;
            else if (value > 64 && value < 71) value -= 55;
            else return type(uint8).max;
        }

        return value;
    }

    /**
     * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
     *
     * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
     *
     * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
     * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
     * characters that are not in this range, but other tooling may provide different results.
     */
    function escapeJSON(string memory input) internal pure returns (string memory) {
        bytes memory buffer = bytes(input);
        bytes memory output = new bytes(2 * buffer.length); // worst case scenario
        uint256 outputLength = 0;

        for (uint256 i; i < buffer.length; ++i) {
            bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
            if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
                output[outputLength++] = "\\";
                if (char == 0x08) output[outputLength++] = "b";
                else if (char == 0x09) output[outputLength++] = "t";
                else if (char == 0x0a) output[outputLength++] = "n";
                else if (char == 0x0c) output[outputLength++] = "f";
                else if (char == 0x0d) output[outputLength++] = "r";
                else if (char == 0x5c) output[outputLength++] = "\\";
                else if (char == 0x22) {
                    // solhint-disable-next-line quotes
                    output[outputLength++] = '"';
                }
            } else {
                output[outputLength++] = char;
            }
        }
        // write the actual length and deallocate unused memory
        assembly ("memory-safe") {
            mstore(output, outputLength)
            mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
        }

        return string(output);
    }

    /**
     * @dev Reads a bytes32 from a bytes array without bounds checking.
     *
     * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
     * assembly block as such would prevent some optimizations.
     */
    function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
        // This is not memory safe in the general case, but all calls to this private function are within bounds.
        assembly ("memory-safe") {
            value := mload(add(add(buffer, 0x20), offset))
        }
    }
}

File 41 of 60 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

import {Arrays} from "../Arrays.sol";
import {Math} from "../math/Math.sol";

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 * - Set can be cleared (all elements removed) in O(n).
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * The following types are supported:
 *
 * - `bytes32` (`Bytes32Set`) since v3.3.0
 * - `address` (`AddressSet`) since v3.3.0
 * - `uint256` (`UintSet`) since v3.3.0
 * - `string` (`StringSet`) since v5.4.0
 * - `bytes` (`BytesSet`) since v5.4.0
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes all the values from a set. O(n).
     *
     * WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that
     * using it may render the function uncallable if the set grows to the point where clearing it consumes too much
     * gas to fit in a block.
     */
    function _clear(Set storage set) private {
        uint256 len = _length(set);
        for (uint256 i = 0; i < len; ++i) {
            delete set._positions[set._values[i]];
        }
        Arrays.unsafeSetLength(set._values, 0);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    /**
     * @dev Return a slice of the set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set, uint256 start, uint256 end) private view returns (bytes32[] memory) {
        unchecked {
            end = Math.min(end, _length(set));
            start = Math.min(start, end);

            uint256 len = end - start;
            bytes32[] memory result = new bytes32[](len);
            for (uint256 i = 0; i < len; ++i) {
                result[i] = Arrays.unsafeAccess(set._values, start + i).value;
            }
            return result;
        }
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Removes all the values from a set. O(n).
     *
     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
     * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
     */
    function clear(Bytes32Set storage set) internal {
        _clear(set._inner);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    /**
     * @dev Return a slice of the set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set, uint256 start, uint256 end) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner, start, end);
        bytes32[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes all the values from a set. O(n).
     *
     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
     * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
     */
    function clear(AddressSet storage set) internal {
        _clear(set._inner);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    /**
     * @dev Return a slice of the set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set, uint256 start, uint256 end) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner, start, end);
        address[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Removes all the values from a set. O(n).
     *
     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
     * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
     */
    function clear(UintSet storage set) internal {
        _clear(set._inner);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    /**
     * @dev Return a slice of the set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set, uint256 start, uint256 end) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner, start, end);
        uint256[] memory result;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }

    struct StringSet {
        // Storage of set values
        string[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(string value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(StringSet storage set, string memory value) internal returns (bool) {
        if (!contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(StringSet storage set, string memory value) internal returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                string memory lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes all the values from a set. O(n).
     *
     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
     * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
     */
    function clear(StringSet storage set) internal {
        uint256 len = length(set);
        for (uint256 i = 0; i < len; ++i) {
            delete set._positions[set._values[i]];
        }
        Arrays.unsafeSetLength(set._values, 0);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(StringSet storage set, string memory value) internal view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(StringSet storage set) internal view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(StringSet storage set, uint256 index) internal view returns (string memory) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(StringSet storage set) internal view returns (string[] memory) {
        return set._values;
    }

    /**
     * @dev Return a slice of the set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(StringSet storage set, uint256 start, uint256 end) internal view returns (string[] memory) {
        unchecked {
            end = Math.min(end, length(set));
            start = Math.min(start, end);

            uint256 len = end - start;
            string[] memory result = new string[](len);
            for (uint256 i = 0; i < len; ++i) {
                result[i] = Arrays.unsafeAccess(set._values, start + i).value;
            }
            return result;
        }
    }

    struct BytesSet {
        // Storage of set values
        bytes[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(BytesSet storage set, bytes memory value) internal returns (bool) {
        if (!contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(BytesSet storage set, bytes memory value) internal returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes memory lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes all the values from a set. O(n).
     *
     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
     * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
     */
    function clear(BytesSet storage set) internal {
        uint256 len = length(set);
        for (uint256 i = 0; i < len; ++i) {
            delete set._positions[set._values[i]];
        }
        Arrays.unsafeSetLength(set._values, 0);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(BytesSet storage set, bytes memory value) internal view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(BytesSet storage set) internal view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(BytesSet storage set, uint256 index) internal view returns (bytes memory) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(BytesSet storage set) internal view returns (bytes[] memory) {
        return set._values;
    }

    /**
     * @dev Return a slice of the set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(BytesSet storage set, uint256 start, uint256 end) internal view returns (bytes[] memory) {
        unchecked {
            end = Math.min(end, length(set));
            start = Math.min(start, end);

            uint256 len = end - start;
            bytes[] memory result = new bytes[](len);
            for (uint256 i = 0; i < len; ++i) {
                result[i] = Arrays.unsafeAccess(set._values, start + i).value;
            }
            return result;
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

import { IDomaRecord } from "../../interfaces/IDomaRecord.sol";
import { IERC7786GatewaySource } from "../../interfaces/IERC7786.sol";
import { CAIPUtils } from "../../utils/CAIPUtils.sol";
import { IProxyDomaRecord } from "../../interfaces/IProxyDomaRecord.sol";
import { NameUtils } from "../../utils/NameUtils.sol";
import { GatewayUtils } from "../../utils/GatewayUtils.sol";

// Capability bit masks
uint256 constant CAPABILITY_REGISTRY_RECORDS_MANAGEMENT = 1; // 1 << 0
uint256 constant CAPABILITY_DNS_RECORDS_MANAGEMENT = 1 << 1; // 2

library LibDoma {
    using Strings for uint256;

    error ChainNotSupported(string chainId);
    error DetokenizationForbidden(string sld, string tld);
    error NameIsNotClaimed(string sld, string tld);
    error NameNotFound(string sld, string tld);
    error TldNotSupported(string tld);

    struct Registrar {
        uint256 ianaId;
        address[] operators;
        address[] complianceOperators;
        uint256 supportedCapabilities;
    }

    /**
     * @notice Tokenization request information.
     * @param registrarIanaId The IANA ID of a sponsoring registrar.
     * @param sld The second-level domain of the name. E.g. "example" in "example.com".
     * @param tld The top-level domain of the name. E.g. "com" in "example.com".
     * @param ownershipTokenChainId The chain ID on which ownership token should be minted, CAIP-2 format.
     * @param ownershipTokenOwnerAddress The address that would own the token on the target chain.
     */
    struct TokenizationRequest {
        uint256 registrarIanaId;
        string sld;
        string tld;
        string ownershipTokenChainId;
        string ownershipTokenOwnerAddress;
    }

    /**
     * @notice Claim request information.
     * @param claimedBy Token owner that performed a name claim.
     * @param proofSource Registrant handle source location. For now, only DOMA value is supported.
     * @param registrantHandle Opaque handle of a registrant. Used to fetch contacts information from the proof source.
     */
    struct ClaimRequest {
        string claimedBy;
        IDomaRecord.ProofOfContactsSource proofSource;
        uint256 registrantHandle;
    }

    /* solhint-disable-next-line gas-struct-packing */
    struct Name {
        /**
         * @dev IANA Id of a sponsoring registrar.
         */
        uint256 registrarIanaId;
        /**
         * @dev Second-Level domain. E.g. "example" in "example.com".
         */
        string sld;
        /**
         * @dev Top-Level domain. E.g. "com" in "example.com".
         */
        string tld;
        /**
         * @dev Registrar Expiration Date of a Name.
         */
        uint256 expiresAt;
        /**
         * @dev Indicates whether it's an Expression of Interest (EOI) name, or a full ICANN Domain.
         */
        bool eoi;
        /**
         * @dev List of nameservers configured for a domain.
         * Only used for ICANN domains.
         */
        string[] nameservers;
        /**
         * @dev List of DNSSEC DSKeys configured for a domain.
         * Only used for ICANN domains.
         */
        IDomaRecord.DSKey[] dsKeys;
        /**
         * @dev Current owner (claimer) of a name (CAIP-10 format).
         * For EOIs, always equals to the `ownedBy` of an ownership token.
         */
        string claimedBy;
        /**
         * @dev Registrar-supported capabilities for a current domain.
         */
        uint256 supportedCapabilities;
        /**
         * @dev List of issued name token ids.
         * Always contains at least one token (ownership token).
         */
        uint256[] nameTokens;
        /**
         * @dev Whether compliance detokenization was requested for this name.
         * Use to prevent some operations, like bridging, if forced detokenization is requested.
         */
        bool complianceDetokenizationRequested;
    }

    struct NameToken {
        /**
         * @dev Globally-unique token ID.
         */
        uint256 tokenId;
        /**
         * @dev CAIP-2 identifier of a target chain.
         */
        string chainId;
        /**
         * @dev Local-chain address of a last-known owner on a target chain.
         * Value is updated by relaying cross-chain messages.
         */
        string ownedBy;
        /**
         * @dev Whether it's an Ownership or Synthetic token type.
         */
        bool isSynthetic;
        /**
         * @dev Whether it's an Ownership token (that gives a claim right to a domain).
         */
        bool ownership;
        /**
         * @dev Capabilities included into this token.
         * Only used for Permissioned tokens.
         */
        uint256 capabilities;
        /**
         * @dev Start date when included capabilities can be used
         * Only used for Permissioned tokens.
         * 0 value means token can be used immediately after mint.
         */
        uint256 startsAt;
        /**
         * @dev End date after which included capabilities can no longer be used.
         * Only used for Permissioned tokens.
         * 0 value means unbound end date (until domain is deleted).
         */
        uint256 expiresAt;
    }

    struct ChainPolicy {
        /**
         * @dev Whether this chain only supports EOI names.
         */
        bool eoiOnly;
        /**
         * @dev List of EOI TLDs supported on this chain.
         */
        string[] eoiTlds;
    }

    struct DomaState {
        /**
         * @dev Address of a cross-chain gateway to send cross-chain messages.
         */
        IERC7786GatewaySource crossChainGateway;
        /**
         * @dev Maps CAIP-2 chain ids to Doma Proxy Contracts Address on a target chain.
         */
        mapping(string => string) proxyContracts;
        /**
         * @dev Mapping to retrieve registrar info by IANA ID.
         */
        mapping(uint256 => Registrar) registrarByIanaId;
        /**
         * @dev Maps registrar operator address to registrar IANA ID.
         * Used for efficient access control checks.
         */
        mapping(address => uint256) registrarByOperator;
        /**
         * @dev Maps registrar compliance operator address to registrar IANA ID.
         * Used for efficient access control checks.
         */
        mapping(address => uint256) registrarByComplianceOperator;
        /**
         * @dev List of accounts with protocol admin role.
         */
        mapping(address => bool) protocolAdmins;
        /**
         * @dev List of accounts with cross-chain sender role.
         */
        mapping(address => bool) crossChainSenders;
        /**
         * @dev Trusted forwarder contract address.
         */
        address trustedForwarder;
        /**
         * @dev Maps tokenization request ID to tokenization request.
         * Tokenization request ID is a namehash of a name (which is also used as ownership token id).
         */
        mapping(uint256 => TokenizationRequest) tokenizationRequests;
        /**
         * @dev Maps name id (namehash) to name info.
         */
        mapping(uint256 => Name) names;
        /**
         * @dev Maps name token id to name token info.
         */
        mapping(uint256 => NameToken) nameTokens;
        /**
         * @dev Maps name token id to name id.
         * Useful for reverse lookup of a name by token id.
         */
        mapping(uint256 => uint256) nameTokensToName;
        /**
         * @dev Maps EOI tlds to registrar IANA ID.
         * This is used to check if Registrar is allowed to issue EOI names.
         */
        mapping(string => uint256) eoiRegistrars;
        /**
         * @dev List of supported TLDs, that have been whitelisted on DOMA Protocol.
         * TLDs on this list are mutually exclusive with EOI tlds (from `eoiRegistrars`).
         */
        mapping(string => bool) tlds;
        /**
         * @dev Maps claim request ID to claim request.
         * Claim request ID is a namehash of a name (which is also used as ownership token id).
         */
        mapping(uint256 => ClaimRequest) claimRequests;
        /**
         * @dev List of precomputed TLDs namehashes.
         * Used as a performance optimization to better support eTLDs.
         */
        mapping(string => uint256) tldHashes;
        /**
         * @dev Doma-controlled nameservers. Implicitly grants CAPABILITY_DNS_RECORDS_MANAGEMENT.
         */
        mapping(string => bool) domaNameservers;
        /**
         * @dev Maps CAIP-2 chain ids to chain policies.
         * Defines which types of names (EOI/ICANN) can be tokenized on each chain.
         */
        mapping(string => ChainPolicy) chainPolicies;
    }

    function domaState() internal pure returns (DomaState storage ds) {
        // By convention, doma state is the first storage slot
        bytes32 position = 0;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            ds.slot := position
        }
    }

    function relayMessage(
        bytes memory data,
        string memory targetChainId,
        uint256 nonceKey,
        string memory correlationId
    ) internal {
        DomaState storage ds = domaState();

        string memory proxyAddress = ds.proxyContracts[targetChainId];
        if (bytes(proxyAddress).length == 0) {
            revert ChainNotSupported(targetChainId);
        }
        bytes[] memory attributes = new bytes[](2);
        attributes[0] = abi.encodeWithSelector(
            GatewayUtils.CORRELATION_ID_ATTRIBUTE,
            correlationId
        );
        attributes[1] = abi.encodeWithSelector(GatewayUtils.NONCE_KEY_ATTRIBUTE, nonceKey);

        ds.crossChainGateway.sendMessage(
            targetChainId,
            CAIPUtils.format(targetChainId, proxyAddress),
            data,
            attributes
        );
    }

    function detokenizeIfClaimed(
        uint256 nameId,
        Name memory name,
        string memory correlationId
    ) internal {
        DomaState storage ds = domaState();

        // Detokenization is forbidden if permissioned synthetic exists
        // We can check it if there's more than one token (ownership token always exists)
        if (name.nameTokens.length > 1) {
            revert LibDoma.DetokenizationForbidden(name.sld, name.tld);
        }

        LibDoma.NameToken memory ownershipToken = ds.nameTokens[nameId];
        if (isClaimedByDomaProxy(name.claimedBy)) {
            revert LibDoma.NameIsNotClaimed(name.sld, name.tld);
        }

        bytes memory detokenizeCalldata = abi.encodeCall(
            IProxyDomaRecord.detokenize,
            (nameId.toString(), ownershipToken.isSynthetic, ownershipToken.ownedBy, correlationId)
        );
        LibDoma.relayMessage(detokenizeCalldata, ownershipToken.chainId, nameId, correlationId);
    }

    function mintOwnershipToken(
        uint256 nameId,
        LibDoma.Name memory name,
        LibDoma.NameToken memory token,
        string memory correlationId
    ) internal {
        IProxyDomaRecord.OwnershipTokenInfo[]
            memory tokens = new IProxyDomaRecord.OwnershipTokenInfo[](1);
        tokens[0] = IProxyDomaRecord.OwnershipTokenInfo(
            name.sld,
            name.tld,
            token.tokenId.toString(),
            name.expiresAt
        );

        // Send only domain-level capabilities (excludes registrar-level)
        uint256[] memory domainCapabilities = new uint256[](1);
        domainCapabilities[0] = LibDoma.calculateDomainLevelCapabilities(
            name.supportedCapabilities,
            name.nameservers
        );

        bytes memory mintOwnershipTokensCalldata = abi.encodeWithSignature(
            // we have to use encodeWithSignature here to match overloaded function
            "mintOwnershipTokens(uint256,(string,string,string,uint256)[],string,uint256[],string)",
            name.registrarIanaId,
            tokens,
            token.ownedBy,
            domainCapabilities,
            correlationId
        );
        LibDoma.relayMessage(mintOwnershipTokensCalldata, token.chainId, nameId, correlationId);
    }

    function ensureTLDSupported(string memory tld) internal view {
        DomaState storage ds = domaState();
        if (!ds.tlds[tld]) {
            revert TldNotSupported(tld);
        }
    }

    function ensureChainSupported(string memory chainId) internal view {
        DomaState storage ds = domaState();
        if (bytes(ds.proxyContracts[chainId]).length == 0) {
            revert ChainNotSupported(chainId);
        }
    }

    function namehash(string memory sld, string memory tld) internal view returns (uint256) {
        DomaState storage ds = domaState();
        uint256 tldNamehash = ds.tldHashes[tld];

        // Safety check to make sure TLD hash has been precomputed
        // We can remove it once migration is done on devnet and testnet
        if (tldNamehash == 0) {
            tldNamehash = NameUtils.namehash(tld);
        }
        return NameUtils.namehash(tldNamehash, sld);
    }

    function isClaimedByDomaProxy(string memory claimedBy) internal view returns (bool) {
        return Strings.equal(claimedBy, domaProxyAddress());
    }

    function domaProxyAddress() internal view returns (string memory) {
        return
            CAIPUtils.format(CAIPUtils.caip2Local(), "0x0000000000000000000000000000000000000000");
    }

    /**
     * @dev Check if nameservers are a subset of Doma-controlled nameservers.
     * @param nameservers Domain's nameserver list to check.
     * @return True if all nameservers are Doma-controlled, false otherwise.
     */
    function isUsingDomaNameservers(string[] memory nameservers) internal view returns (bool) {
        if (nameservers.length == 0) return false;

        DomaState storage ds = domaState();
        for (uint256 i = 0; i < nameservers.length; i++) {
            if (!ds.domaNameservers[nameservers[i]]) {
                return false;
            }
        }
        return true;
    }

    /**
     * @notice Check if a specific capability is present in the capabilities bit field
     * @param capabilities The combined capabilities bit field
     * @param requiredCapability The required capability bit mask
     * @return True if the capability is present
     */
    function hasCapability(
        uint256 capabilities,
        uint256 requiredCapability
    ) internal pure returns (bool) {
        return (capabilities & requiredCapability) == requiredCapability;
    }

    /**
     * @notice Calculate effective capabilities for a name
     * @dev Combines registrar-level and domain-level capabilities with implicit DNS capability
     * @param registrarCapabilities The registrar's supported capabilities
     * @param domainCapabilities The domain's supported capabilities
     * @param nameservers The domain's nameserver list
     * @return effectiveCapabilities The combined effective capabilities
     */
    function calculateEffectiveCapabilities(
        uint256 registrarCapabilities,
        uint256 domainCapabilities,
        string[] memory nameservers
    ) internal view returns (uint256 effectiveCapabilities) {
        // Base capabilities = registrar + domain
        effectiveCapabilities = registrarCapabilities | domainCapabilities;

        // Add implicit DNS capability if using Doma nameservers
        if (isUsingDomaNameservers(nameservers)) {
            effectiveCapabilities |= CAPABILITY_DNS_RECORDS_MANAGEMENT;
        }
    }

    /**
     * @notice Calculate domain-level capabilities only (excludes registrar-level capabilities)
     * @dev Used for propagating capabilities to tokenization chains where only domain-specific
     * capabilities need to be stored. Registrar-level capabilities remain on the DOMA chain
     * and are combined during authorization checks.
     * @param domainCapabilities The domain's explicit capabilities
     * @param nameservers The domain's nameserver list
     * @return capabilities The combined domain-level capabilities (explicit + implicit)
     */
    function calculateDomainLevelCapabilities(
        uint256 domainCapabilities,
        string[] memory nameservers
    ) internal view returns (uint256 capabilities) {
        // Start with explicit domain capabilities
        capabilities = domainCapabilities;

        // Add implicit DNS capability if using Doma nameservers
        if (isUsingDomaNameservers(nameservers)) {
            capabilities |= CAPABILITY_DNS_RECORDS_MANAGEMENT;
        }
    }
}

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

interface ICreatorToken {
    /**
     * @notice Emitted when the transfer validator is updated
     * @param oldValidator The previous transfer validator address
     * @param newValidator The new transfer validator address
     */
    event TransferValidatorUpdated(address oldValidator, address newValidator);

    /**
     * @notice Returns the current transfer validator address
     * @return validator The address of the transfer validator contract
     */
    function getTransferValidator() external view returns (address validator);

    /**
     * @notice Sets a new transfer validator
     * @param validator The address of the new transfer validator contract
     */
    function setTransferValidator(address validator) external;

    /**
     * @notice Returns the function signature used for transfer validation
     * @return functionSignature The function selector for validation
     * @return isViewFunction Whether the validation function is a view function
     */
    function getTransferValidationFunction()
        external
        view
        returns (bytes4 functionSignature, bool isViewFunction);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

interface IDomaRecord {
    struct NameInfo {
        string sld;
        string tld;
    }

    struct DSKey {
        uint32 keyTag;
        uint8 algorithm;
        uint8 digestType;
        bytes digest;
    }

    enum ProofOfContactsSource {
        NONE,
        REGISTRAR,
        DOMA
    }

    function initiateTokenization(
        uint256 registrarIanaId,
        NameInfo[] calldata names,
        string calldata ownershipTokenChainId,
        string calldata ownershipTokenOwnerAddress,
        string calldata correlationId
    ) external;

    function claimOwnership(
        string calldata tokenId,
        string calldata chainId,
        string calldata claimedBy,
        ProofOfContactsSource proofSource,
        uint256 registrantHandle,
        string calldata correlationId
    ) external;

    function bridge(
        string calldata tokenId,
        string calldata targetChainId,
        string calldata targetOwnerAddress,
        string calldata correlationId
    ) external;

    function ownerDetokenize(
        string calldata tokenId,
        string calldata chainId,
        string calldata ownerAddress,
        string calldata correlationId
    ) external;

    function completeDetokenization(
        string calldata tokenId,
        string calldata correlationId
    ) external;

    function tokenTransfer(
        string calldata chainId,
        string calldata tokenId,
        string calldata oldOwnerAddress,
        string calldata newOwnerAddress,
        string calldata correlationId
    ) external;

    function setNameservers(
        uint256 tokenId,
        string[] calldata nameservers,
        string calldata correlationId
    ) external;

    function setDSKeys(
        uint256 tokenId,
        DSKey[] calldata dsKeys,
        string calldata correlationId
    ) external;

    function setDNSRRSet(
        uint256 tokenId,
        string calldata host,
        string calldata recordType,
        uint32 ttl,
        string[] calldata records,
        string calldata correlationId
    ) external;

    function convertToSynthetic(
        uint256 tokenId,
        string calldata chainId,
        string calldata owner,
        string calldata correlationId
    ) external;

    function completeSyntheticConversion(
        string calldata tokenId,
        string calldata correlationId
    ) external;

    function convertToOwnership(
        uint256 tokenId,
        string calldata chainId,
        string calldata owner,
        string calldata correlationId
    ) external;

    function completeOwnershipConversion(
        string calldata tokenId,
        string calldata correlationId
    ) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

interface IERC7786GatewaySource {
    event MessagePosted(
        bytes32 indexed outboxId,
        string sender,
        string receiver,
        bytes payload,
        uint256 value,
        bytes[] attributes
    );

    error UnsupportedAttribute(bytes4 selector);

    function supportsAttribute(bytes4 selector) external view returns (bool);

    function sendMessage(
        string calldata destinationChain, // CAIP-2 chain identifier
        string calldata receiver, // CAIP-10 account address
        bytes calldata payload,
        bytes[] calldata attributes
    ) external payable returns (bytes32 outboxId);
}

interface IERC7786Receiver {
    function executeMessage(
        string calldata sourceChain, // [CAIP-2] chain identifier
        string calldata sender, // [CAIP-10] account address
        bytes calldata payload,
        bytes[] calldata attributes
    ) external payable returns (bytes4);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

interface IProxyDomaRecord {
    struct OwnershipTokenInfo {
        string sld;
        string tld;
        string tokenId;
        uint256 expiresAt;
    }

    struct SyntheticTokenMintInfo {
        uint256 tokenId;
        uint256 registrarIanaId;
        string owner;
        bool revocable;
        string sld;
        string tld;
        uint256 expiresAt;
        uint256 capabilities;
        string host;
        uint256 parentTokenId;
        uint256 groupId;
    }

    function mintOwnershipTokens(
        uint256 registrarIanaId,
        OwnershipTokenInfo[] calldata tokens,
        string calldata ownerAddress,
        uint256[] calldata domainCapabilities,
        string calldata correlationId
    ) external;

    // override to retain backward compatibility
    function mintOwnershipTokens(
        uint256 registrarIanaId,
        OwnershipTokenInfo[] calldata tokens,
        string calldata ownerAddress,
        string calldata correlationId
    ) external;

    function mintSyntheticFromOwnership(
        SyntheticTokenMintInfo calldata info,
        string calldata correlationId
    ) external;

    function mintOwnershipFromSynthetic(
        OwnershipTokenInfo calldata info,
        string calldata ownerAddress,
        uint256 registrarIanaId,
        uint256 domainCapabilities,
        string calldata correlationId
    ) external;

    function renew(
        string calldata tokenId,
        bool isSynthetic,
        uint256 expiresAt,
        string calldata correlationId
    ) external;

    function detokenize(
        string calldata tokenId,
        bool isSynthetic,
        string calldata claimedBy,
        string calldata correlationId
    ) external;

    function detokenizeUnchecked(
        string calldata tokenId,
        bool isSynthetic,
        string calldata correlationId
    ) external;

    function changeLockStatus(
        string calldata tokenId,
        bool isSynthetic,
        bool isTransferLocked,
        string calldata correlationId
    ) external;

    function updateDomainCapabilities(
        string calldata tokenId,
        uint256 capabilities,
        string calldata correlationId
    ) external;

    function tokenTransfer(uint256 tokenId, address from, address to) external;

    function setDNSRRSet(
        uint256 tokenId,
        string calldata host,
        string calldata recordType,
        uint32 ttl,
        string[] calldata records,
        bool isSynthetic
    ) external payable;

    function setDNSRRSet(
        uint256 tokenId,
        string calldata host,
        string calldata recordType,
        uint32 ttl,
        string[] calldata records
    ) external payable;

    function convertToSynthetic(uint256 tokenId) external payable;

    function convertToOwnership(uint256 tokenId) external payable;

    function mintSyntheticSubdomain(
        uint256 parentTokenId,
        string calldata host,
        uint256 capabilities,
        uint256 expiresAt,
        bool revocable,
        uint256 groupId,
        address receiver
    ) external payable returns (uint256);

    function revokeGroup(uint256 parentTokenId, uint256 groupId) external;

    function renounceSynthetic(uint256 tokenId) external;

    function revokeSynthetic(uint256 tokenId) external;

    function isLabelForbidden(string calldata label) external view returns (bool);

    function syntheticToken() external view returns (address);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

/**
 * @title ISyntheticToken
 * @notice Interface for SyntheticToken functions used by fractionalization contracts
 */
interface ISyntheticToken {
    /**
     * @notice Get the owner of a token, even if it has been burned.
     * @dev If the token exists, returns the current owner via ownerOf().
     *      If the token has been burned, returns the last owner stored before burning.
     *      Used to allow users to claim staked tokens after their subdomain NFT is revoked and burned.
     * @param tokenId The ID of the token.
     * @return The address of the current owner if token exists, or last owner if burned.
     */
    function lastOwnerOf(uint256 tokenId) external view returns (address);

    /**
     * @notice Check if a subdomain is operational (not revoked or expired)
     * @param parentTokenId The parent domain token ID
     * @param label The subdomain label
     * @return True if operational, false otherwise
     */
    function isSubdomainOperational(
        uint256 parentTokenId,
        string calldata label
    ) external view returns (bool);
}

File 48 of 60 : ITransferValidator.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ITransferValidator {
    /**
     * @notice Validates a token transfer with token ID
     * @param caller The address initiating the transfer
     * @param from The address sending the token
     * @param to The address receiving the token
     * @param tokenId The ID of the token being transferred
     */
    function validateTransfer(
        address caller,
        address from,
        address to,
        uint256 tokenId
    ) external view;
}

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

interface ITransferValidatorSetTokenType {
    /**
     * @notice Sets the token type for a collection
     * @param collection The address of the collection contract
     * @param tokenType The type of token (e.g., ERC721, ERC1155, ERC20)
     */
    function setTokenTypeOfCollection(address collection, uint16 tokenType) external;
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/proxy/utils/UUPSUpgradeable.sol";
import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable-v4/token/ERC721/ERC721Upgradeable.sol";
import { ERC721BurnableUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/access/AccessControlUpgradeable.sol";
import { IProxyDomaRecord } from "./interfaces/IProxyDomaRecord.sol";
import { ICreatorToken } from "./interfaces/ICreatorToken.sol";
import { IERC2981 } from "@openzeppelin/contracts/interfaces/IERC2981.sol";
import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";
import { ERC721C } from "./utils/ERC721C.sol";
import { AccessControlOwnable } from "./utils/AccessControlOwnable.sol";
import { DateUtils } from "./utils/DateUtils.sol";

/**
 * @title NameToken
 * @notice Abstract base contract for name NFTs in the DOMA protocol.
 * @dev This contract implements an ERC721 NFT that represents tokenized names.
 * Features include expiration tracking, transfer locking, name capabilities management,
 * royalty support (ERC2981), and integration with proxy DOMA record contracts.
 */
abstract contract NameToken is
    UUPSUpgradeable,
    ERC721Upgradeable,
    ERC721BurnableUpgradeable,
    AccessControlOwnable,
    ERC721C,
    IERC165,
    IERC2981
{
    /**
     * @dev Storage structure for ERC2981 royalty information.
     */
    struct ERC2981RoyaltyInfo {
        address receiver;
        uint96 royaltyFraction;
    }

    /**
     * @dev Storage structure for ERC2981 royalty information.
     * Contains the contract-wide royalty information that applies to all tokens.
     */
    /// @custom:storage-location erc7201:doma.storage.ERC2981
    struct ERC2981Storage {
        ERC2981RoyaltyInfo royaltyInfo;
    }

    // Storage slot for ERC2981 storage
    bytes32 private constant _ERC2981_STORAGE_SLOT =
        keccak256(abi.encode(uint256(keccak256(bytes("doma.storage.ERC2981"))) - 1)) &
            ~bytes32(uint256(0xff));

    /**
     * @dev Role required to mint tokens.
     * Equals to 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6
     */
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    /**
     * @dev Role required to update metadata.
     */
    bytes32 public constant METADATA_OWNER_ROLE = keccak256("METADATA_OWNER_ROLE");

    /**
     * @notice Thrown when attempting to transfer a token that is locked for transfers.
     * @param tokenId The ID of the locked token
     */
    error TransferLocked(uint256 tokenId);

    /**
     * @notice Thrown when owner attempts to use burn functionality that is not supported.
     */
    error OwnerBurnNotSupported();

    /**
     * @notice Thrown when proxy DOMA record address is not set but required for operation.
     */
    error ProxyDomaRecordNotSet();

    /**
     * @notice Thrown when a zero address is provided where a valid address is required.
     */
    error ZeroAddress();

    /**
     * @notice Thrown when an invalid royalty receiver address is provided.
     */
    error InvalidRoyaltyReceiver();

    /**
     * @notice Thrown when royalty fraction exceeds the maximum allowed value.
     * @param feeNumerator The provided fee numerator
     * @param feeDenominator The fee denominator (10000 for basis points)
     */
    error InvalidRoyaltyFraction(uint96 feeNumerator, uint96 feeDenominator);

    /**
     * @notice Thrown when array parameters have mismatched lengths.
     */
    error ArrayLengthMismatch();

    /**
     * @notice Thrown when the token does not exist
     */
    error NameTokenDoesNotExist(uint256 tokenId);

    /**
     * @notice Emitted when name token is renewed.
     * @param tokenId The ID of the name token.
     * @param expiresAt The expiration date of the name token (UNIX seconds).
     * @param correlationId Correlation id associated with a renewal event. Used by registrars to track on-chain operations.
     */
    event NameTokenRenewed(uint256 indexed tokenId, uint256 expiresAt, string correlationId);

    /**
     * @notice Emitted when name token is burned.
     * Similar to ERC721 `Transfer` event with zero `to`, but with an additional correlation id included.
     * @param tokenId The ID of the name token.
     * @param owner Owner address at the time of burning.
     * @param correlationId Correlation id associated with a burn event. Used by registrars to track on-chain operations.
     */
    event NameTokenBurned(uint256 indexed tokenId, address owner, string correlationId);

    /**
     * @notice Emitted when name token is locked or unlocked.
     * @param tokenId The ID of the name token.
     * @param isTransferLocked Whether token transfer is locked or not.
     * @param correlationId Correlation id associated with a lock status change event. Used by registrars to track on-chain operations.
     */
    event LockStatusChanged(uint256 indexed tokenId, bool isTransferLocked, string correlationId);

    /**
     * @notice Emitted when metadata is updated for a token.
     * Can happen when token is renewed.
     * Follows IERC4906 Metadata Update Extension.
     */
    event MetadataUpdate(uint256 tokenId);

    /**
     * @notice Emitted when domain-level capabilities are updated for a token.
     * @param tokenId The ID of the name token.
     * @param capabilities The new domain capabilities bitmask.
     * @param correlationId Correlation id for tracking operations.
     */
    event DomainCapabilitiesUpdated(
        uint256 indexed tokenId,
        uint256 capabilities,
        string correlationId
    );

    /**
     * @dev Base URI for NFT tokens.
     */
    string internal _baseTokenURI;

    /**
     * @dev TLDs list is deprecated and no longer used.
     * Variable is left in place to keep storage layout the same.
     * In future, this storage slot could be reused for a different purpose.
     */
    mapping(string => uint256) internal _tlds;

    /**
     * @dev Maps Token ID to its expiration date.
     * Expiration date is a timestamp in seconds.
     * Since contract guarantees there could be only one token with a given ID, we can use Token ID as a key.
     */
    /// @custom:oz-renamed-from expirations
    mapping(uint256 => uint256) internal _expirations;

    /**
     * @dev Maps Token ID to its lock status
     * If set to true, token cannot be transferred
     */
    /// @custom:oz-renamed-from transferLocks
    mapping(uint256 => bool) internal _transferLocks;

    /**
     * @dev Storage gap, from previous version of the contract, to keep storage layout the same
     * Storage slot from a removed `gracePeriod` variable is reused
     * We can do this since mappings don't store any data in a storage slot
     */
    /// @custom:oz-renamed-from gracePeriod
    /// @custom:oz-retyped-from uint256
    mapping(uint256 => uint256) internal _registrarIanaIds;

    /**
     * @dev Block all transfers and minting. Used to prevent transfers during migration.
     */
    bool public blockAllTransfers;

    /**
     * @dev Address if ProxyDomaRecord Contract for proxy call
     */
    IProxyDomaRecord public proxyDomaRecord;

    /**
     * @dev Storage gap for future upgrades.
     */
    uint256[50] private _storageGap;

    /**
     * @dev Maps Token ID to its domain-level capabilities.
     * These are the capabilities supported by this specific domain.
     * Propagated automatically from Doma Chain when capabilities change.
     */
    mapping(uint256 => uint256) internal _domainCapabilities;

    modifier onlyMinter() {
        _checkRole(MINTER_ROLE);
        _;
    }

    modifier onlyOwner() {
        _checkRole(DEFAULT_ADMIN_ROLE);
        _;
    }

    modifier onlyMetadataOwner() {
        _checkRole(METADATA_OWNER_ROLE);
        _;
    }

    /**
     * @notice Constructor that disables initializers on an implementation contract.
     * @dev This is recommended for upgradable contracts.
     */
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initialize the contract with name, symbol, URI, and initial owner.
     * @dev Acts as constructor for upgradeable contract. Sets up roles and inheritance chain.
     * The initial owner receives DEFAULT_ADMIN_ROLE and can manage other roles.
     * @param name Name for the NFT collection (different contract instances may have different names)
     * @param symbol Symbol for the NFT collection (usually a short version of the name)
     * @param uri Base URI for NFT token metadata (typically points to off-chain mutable storage like S3)
     * @param initialOwner Address that will receive admin role and contract ownership
     */
    function initialize(
        string calldata name,
        string calldata symbol,
        string calldata uri,
        address initialOwner
    ) public initializer {
        __UUPSUpgradeable_init();
        __AccessControl_init();
        __ERC721_init(name, symbol);
        __ERC721Burnable_init();

        if (initialOwner == address(0)) {
            revert ZeroAddress();
        }

        _setupRole(DEFAULT_ADMIN_ROLE, initialOwner);
        _setRoleAdmin(MINTER_ROLE, DEFAULT_ADMIN_ROLE);
        _setRoleAdmin(METADATA_OWNER_ROLE, DEFAULT_ADMIN_ROLE);

        _baseTokenURI = uri;
    }

    /**
     * @notice User-initiated burn is not supported.
     * @dev This override prevents direct token burning by users. Only the bulkBurn function
     * should be used by authorized minters to ensure proper cleanup and event emission.
     * Base class (ERC721BurnableUpgradeable) is kept for compatibility with previous storage layout.
     */
    function burn(uint256) public virtual override {
        revert OwnerBurnNotSupported();
    }

    /**
     * @dev Internal function to burn a name token and clean up all associated storage.
     * @param tokenId The ID of the token to burn.
     * @param correlationId Correlation ID for tracking the burn operation.
     */
    function _burnNameToken(uint256 tokenId, string memory correlationId) internal {
        address owner = ownerOf(tokenId);
        delete _domainCapabilities[tokenId];
        delete _expirations[tokenId];
        delete _transferLocks[tokenId];
        delete _registrarIanaIds[tokenId];
        _burn(tokenId);
        emit NameTokenBurned(tokenId, owner, correlationId);
    }

    /**
     * @notice Trigger metadata update event for a specific token.
     * @dev Only metadata owners can call this function. Follows IERC4906 Metadata Update Extension.
     * This is useful when off-chain metadata changes and the update needs to be signaled on-chain.
     * @param tokenId The ID of the token whose metadata was updated
     */
    function metadataUpdated(uint256 tokenId) public onlyMetadataOwner {
        emit MetadataUpdate(tokenId);
    }

    /**
     * @notice Returns expiration date for a token. After this date, token transfer will be blocked.
     * @param id Token ID.
     * @return uint256 Unix timestamp in seconds.
     */
    function expirationOf(uint256 id) external view virtual returns (uint256) {
        return _expirations[id];
    }

    /**
     * @notice Returns registrar IANA ID for a token.
     * @param id Token ID.
     * @return uint256 Registrar IANA ID.
     */
    function registrarOf(uint256 id) external view returns (uint256) {
        return _registrarIanaIds[id];
    }

    /**
     * @notice Returns transfer lock status for a token. If 'true', token cannot be transferred.
     * @param id Token ID.
     */
    function lockStatusOf(uint256 id) external view returns (bool) {
        return _transferLocks[id] || blockAllTransfers;
    }

    /**
     * @notice Returns domain-level capabilities for a token.
     * These are the capabilities supported by this specific domain.
     * @param id Token ID.
     * @return uint256 Domain capabilities bitmask.
     */
    function domainCapabilitiesOf(uint256 id) external view returns (uint256) {
        return _domainCapabilities[id];
    }

    /**
     * @notice Check if a token has a specific capability.
     * @param id Token ID.
     * @param requiredCapability The required capability bit mask.
     * @return bool True if the token has the capability.
     */
    function hasCapability(uint256 id, uint256 requiredCapability) external view returns (bool) {
        uint256 capabilities = _domainCapabilities[id];
        return (capabilities & requiredCapability) == requiredCapability;
    }

    /**
     * @notice Returns true if a token with the given ID exists.
     * @param id Token ID.
     */
    function exists(uint256 id) external view returns (bool) {
        return _exists(id);
    }

    /**
     * @notice Overrides behavior of isApprovedForAll such that if an operator is not explicitly approved
     *         for all, the contract owner can optionally auto-approve the transfer validator for transfers.
     * @param owner The owner of the tokens
     * @param operator The operator to check approval for
     * @return isApproved True if the operator is approved for all tokens
     */
    function isApprovedForAll(
        address owner,
        address operator
    ) public view virtual override(ERC721Upgradeable) returns (bool isApproved) {
        isApproved = super.isApprovedForAll(owner, operator);

        if (!isApproved) {
            ERC721C.ERC721CStorage storage storage_ = _erc721Storage();
            if (storage_.autoApproveTransfersFromValidator) {
                isApproved = operator == getTransferValidator();
            }
        }
    }

    /**
     * @notice Check if the contract supports a given interface.
     * @dev Extends the standard ERC721 interface support to include additional interfaces:
     * Metadata Update (EIP-4906), Creator Token, ERC2981 Royalty, and Ownable interfaces.
     * @param interfaceId The interface identifier to check
     * @return Whether the contract supports the interface
     */
    function supportsInterface(
        bytes4 interfaceId
    )
        public
        view
        virtual
        override(ERC721Upgradeable, AccessControlUpgradeable, IERC165)
        returns (bool)
    {
        // Additional check for Metadata Update interface, ICreatorToken interface, IERC2981 interface, and Ownable interface
        return
            interfaceId == bytes4(0x49064906) || // Metadata Update interface
            interfaceId == type(ICreatorToken).interfaceId ||
            interfaceId == bytes4(0x7f5828d0) || // Ownable interface
            super.supportsInterface(interfaceId);
    }

    /**
     * @notice Get royalty information for a token sale (ERC2981).
     * @dev Returns the royalty receiver and amount for a given sale price.
     * Royalty is calculated as (salePrice * royaltyFraction) / 10000.
     * @param salePrice The sale price of the token
     * @return receiver The address that should receive the royalty payment
     * @return royaltyAmount The royalty amount to be paid
     */
    function royaltyInfo(
        uint256,
        uint256 salePrice
    ) external view virtual override returns (address receiver, uint256 royaltyAmount) {
        ERC2981Storage storage storage_ = _erc2981Storage();
        ERC2981RoyaltyInfo storage royalty = storage_.royaltyInfo;

        uint256 royaltyFraction = royalty.royaltyFraction;
        receiver = royalty.receiver;
        royaltyAmount = (salePrice * royaltyFraction) / _feeDenominator();
    }

    /**
     * @notice Update the base URI for all tokens in the collection.
     * @dev Admin-only function that affects metadata for all existing and future tokens.
     * Should be used with care as it impacts the entire collection.
     * @param newUri The new base URI to set
     */
    function setURI(string calldata newUri) external onlyOwner {
        _baseTokenURI = newUri;
    }

    /**
     * @notice Enable or disable all token transfers globally.
     * @dev Admin-only function used to block transfers during migration or emergency situations.
     * When enabled, no tokens can be transferred regardless of individual lock status.
     * @param newBlockAllTransfers True to block all transfers, false to allow normal operation
     */
    function setBlockAllTransfers(bool newBlockAllTransfers) external onlyOwner {
        blockAllTransfers = newBlockAllTransfers;
    }

    /**
     * @notice Set transfer lock status for a specific token.
     * @dev Minter-only function to prevent or allow transfers of individual tokens.
     * Emits LockStatusChanged event for tracking purposes.
     * @param id The token ID to modify
     * @param isTransferLocked True to lock transfers, false to unlock
     * @param correlationId Correlation ID for tracking the operation
     */
    function setLockStatus(
        uint256 id,
        bool isTransferLocked,
        string calldata correlationId
    ) external onlyMinter {
        _transferLocks[id] = isTransferLocked;
        emit LockStatusChanged(id, isTransferLocked, correlationId);
    }

    /**
     * @notice Update domain capabilities for a specific token.
     * @dev Minter-only function called when capabilities are updated on DOMA chain.
     * Domain capabilities determine what operations the token holder can perform.
     * @param id The token ID to update
     * @param capabilities The new domain capabilities bitmask
     * @param correlationId Correlation ID for tracking the operation
     */
    function updateDomainCapabilities(
        uint256 id,
        uint256 capabilities,
        string calldata correlationId
    ) external onlyMinter {
        _domainCapabilities[id] = capabilities;
        emit DomainCapabilitiesUpdated(id, capabilities, correlationId);
    }

    /**
     * @notice Set the address of the ProxyDomaRecord contract.
     * @dev Admin-only function to update the proxy contract reference.
     * Used for cross-chain communication and operations.
     * @param proxyDomaRecord_ The new ProxyDomaRecord contract address
     */
    function setProxyDomaRecord(IProxyDomaRecord proxyDomaRecord_) external onlyOwner {
        if (address(proxyDomaRecord_) == address(0)) revert ZeroAddress();
        proxyDomaRecord = proxyDomaRecord_;
    }

    /**
     * @notice Set royalty information for all tokens in the collection.
     * @dev Admin-only function implementing ERC2981 royalty standard.
     * Royalties are paid as (salePrice * feeNumerator) / 10000.
     * @param receiver Address that should receive royalty payments
     * @param feeNumerator The royalty fee numerator (basis points, max 10000)
     */
    function setRoyaltyInfo(address receiver, uint96 feeNumerator) external onlyOwner {
        _setRoyaltyInfo(receiver, feeNumerator);
    }

    /**
     * @notice Remove royalty information from the contract.
     * @dev Admin-only function to disable royalties for all tokens.
     */
    function deleteRoyaltyInfo() external onlyOwner {
        _deleteRoyaltyInfo();
    }

    function _mint(
        uint256 registrarIanaId,
        uint256 tokenId,
        uint256 expiresAt,
        address to
    ) internal {
        DateUtils.validateExpirationDate(expiresAt);

        _safeMint(to, tokenId);

        _expirations[tokenId] = expiresAt;
        _registrarIanaIds[tokenId] = registrarIanaId;
    }

    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    /**
     * @dev Overridden to support token expiration and ERC721C validation.
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256
    ) internal virtual override {
        // Handle only transfer between owners
        // Burn is not public and handled internally
        // Mint has a special logic to handle expirations
        if (from != address(0) && to != address(0)) {
            if (
                _isTokenExpired(firstTokenId) || _transferLocks[firstTokenId] || blockAllTransfers
            ) {
                revert TransferLocked(firstTokenId);
            }

            // Revert if proxyDomaRecord is not set
            if (address(proxyDomaRecord) == address(0)) {
                revert ProxyDomaRecordNotSet();
            }
            proxyDomaRecord.tokenTransfer(firstTokenId, from, to);
            // Call ERC721C validation
            _preValidateTransfer(_msgSender(), from, to, firstTokenId, msg.value);
        }
    }

    function _isTokenExpired(uint256 id) internal view virtual returns (bool) {
        return _expirations[id] < block.timestamp;
    }

    /* solhint-disable-next-line no-empty-blocks */
    function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {}

    /**
     * @dev Implementation required by ERC721C for contract ownership verification.
     * Uses the DEFAULT_ADMIN_ROLE to determine contract ownership.
     */
    function _requireCallerIsContractOwner() internal view virtual override {
        _checkRole(DEFAULT_ADMIN_ROLE);
    }

    function _setRoyaltyInfo(address receiver, uint96 feeNumerator) internal virtual {
        if (feeNumerator > _feeDenominator()) {
            revert InvalidRoyaltyFraction(feeNumerator, _feeDenominator());
        }
        if (receiver == address(0)) {
            revert InvalidRoyaltyReceiver();
        }

        ERC2981Storage storage storage_ = _erc2981Storage();
        storage_.royaltyInfo = ERC2981RoyaltyInfo(receiver, feeNumerator);
    }

    /**
     * @dev Internal function to remove all royalty information from storage.
     */
    function _deleteRoyaltyInfo() internal virtual {
        delete _erc2981Storage().royaltyInfo;
    }

    function version() external pure virtual returns (string memory);

    function _erc2981Storage() internal pure returns (ERC2981Storage storage storageStruct) {
        bytes32 slot = _ERC2981_STORAGE_SLOT;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            storageStruct.slot := slot
        }
    }

    /**
     * @dev Returns the denominator for royalty fee calculations.
     * @return The fee denominator (10000, representing basis points)
     */
    function _feeDenominator() internal pure virtual returns (uint96) {
        return 10000;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { NameToken } from "./NameToken.sol";
import { DateUtils } from "./utils/DateUtils.sol";

contract OwnershipToken is NameToken {
    /**
     * @notice Emitted when an ownership token is minted.
     * Emitted together with standard ERC-721 Transfer event, but contains additional information.
     * @param tokenId The ID of the ownership token.
     * @param registrarIanaId The IANA ID of a sponsoring registrar.
     * @param to The address that received the ownership token.
     * @param sld The second-level domain of the name. E.g. "example" in "example.com".
     * @param tld The top-level domain of the name. E.g. "com" in "example.com".
     * @param expiresAt The expiration date of the name (UNIX seconds).
     * @param correlationId Correlation id associated with a mint event. Used by registrars to track on-chain operations.
     */
    event OwnershipTokenMinted(
        uint256 indexed tokenId,
        uint256 registrarIanaId,
        address to,
        string sld,
        string tld,
        uint256 expiresAt,
        string correlationId
    );

    struct OwnershipTokenMintInfo {
        uint256 registrarIanaId;
        string sld;
        string tld;
        uint256 tokenId;
        uint256 expiresAt;
        address owner;
    }

    /**
     * @notice Bulk mint tokens without domain capabilities. Could be called only by a minter role.
     * @param names Names to mint.
     * @param correlationId Correlation id associated with a given mint request.
     */
    function bulkMint(
        OwnershipTokenMintInfo[] calldata names,
        string calldata correlationId
    ) public {
        // onlyMinter modifier on the overloaded function
        uint256[] memory domainCapabilities = new uint256[](names.length);
        // Array is initialized with zero capabilities by default
        bulkMint(names, domainCapabilities, correlationId);
    }

    /**
     * @notice Bulk mint tokens with domain capabilities. Could be called only by a minter role.
     * @param names Names to mint.
     * @param domainCapabilities Domain capabilities for each token (must match names length, use 0 for no capabilities).
     * @param correlationId Correlation id associated with a given mint request.
     */
    function bulkMint(
        OwnershipTokenMintInfo[] calldata names,
        uint256[] memory domainCapabilities,
        string calldata correlationId
    ) public onlyMinter {
        if (names.length != domainCapabilities.length) {
            revert ArrayLengthMismatch();
        }

        for (uint256 i = 0; i < names.length; i++) {
            OwnershipTokenMintInfo memory name = names[i];
            _mint(name.registrarIanaId, name.tokenId, name.expiresAt, name.owner);

            // Set domain capabilities atomically during mint
            if (domainCapabilities[i] != 0) {
                _domainCapabilities[name.tokenId] = domainCapabilities[i];
            }

            emit OwnershipTokenMinted(
                name.tokenId,
                name.registrarIanaId,
                name.owner,
                name.sld,
                name.tld,
                name.expiresAt,
                correlationId
            );

            // always emit even if capabilities are zero
            emit DomainCapabilitiesUpdated(name.tokenId, domainCapabilities[i], correlationId);
        }
    }

    /**
     * @notice Renew SLD token. Could be called only by a minter role.
     * @param tokenId Name token ID to renew.
     * @param expiresAt Expiration date for a token (Unix timestamp seconds).
     * @param correlationId Order id associated with a given mint.
     */
    function renew(
        uint256 tokenId,
        uint256 expiresAt,
        string calldata correlationId
    ) public onlyMinter {
        DateUtils.validateExpirationDate(expiresAt);

        address currentOwner = _ownerOf(tokenId);
        if (currentOwner == address(0)) {
            revert NameTokenDoesNotExist(tokenId);
        }

        _expirations[tokenId] = expiresAt;

        emit NameTokenRenewed(tokenId, expiresAt, correlationId);
    }

    /**
     * @notice Burn multiple ownership tokens.
     * @dev Only authorized minters can call this function.
     * @param tokenIds Array of token IDs to burn.
     * @param correlationId Correlation ID associated with the burn operation for tracking.
     */
    function bulkBurn(
        uint256[] calldata tokenIds,
        string calldata correlationId
    ) public onlyMinter {
        uint256 len = tokenIds.length;
        for (uint256 i = 0; i < len; ) {
            _burnNameToken(tokenIds[i], correlationId);
            unchecked {
                i++;
            }
        }
    }

    function version() external pure override returns (string memory) {
        return "2.1.0";
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import { IERC7786GatewaySource } from "../../interfaces/IERC7786.sol";
import { NameToken } from "../../NameToken.sol";
import { OwnershipToken } from "../../OwnershipToken.sol";
import { SyntheticToken } from "../../SyntheticToken.sol";
import { CAIPUtils } from "../../utils/CAIPUtils.sol";
import { GatewayUtils } from "../../utils/GatewayUtils.sol";
import { NameUtils } from "../../utils/NameUtils.sol";
import { CAPABILITY_DNS_RECORDS_MANAGEMENT } from "../../doma-record/libraries/LibDoma.sol";

library LibProxyDomaRecord {
    using Strings for uint256;

    struct ProxyDomaRecordStorage {
        /**
         * @dev List of allowed EIP-712 voucher signers for each registrar.
         * Maps signer to registrar IANA ID, to be able to lookup registrar from a current signer.
         */
        mapping(address => uint256) registrarSigners;
        /**
         * @dev List of allowed EIP-712 voucher signers for doma protocol.
         * Used to verify Doma-provided proof-of-contacts vouchers.
         */
        mapping(address => bool) domaSigners;
        /**
         * @dev Used voucher nonces.
         * Necessary to prevent replay attacks.
         */
        mapping(uint256 => bool) nonces;
        /**
         * @dev Address of a cross-chain gateway to send cross-chain messages.
         */
        IERC7786GatewaySource crossChainGateway;
        /**
         * @dev Address of an ownership token contract.
         */
        OwnershipToken ownershipToken;
        /**
         * @dev CAIP-2 Chain ID of the Doma Chain.
         */
        string domaChainId;
        /**
         * @dev CAIP-10 Address of a Doma Record Contract on Doma Chain.
         */
        string domaRecordAddress;
        /**
         * @dev Internal lists to keep track of registrar signers for enumeration.
         */
        mapping(uint256 => address[]) registrarSignerList;
        address[] domaSignerList;
        /**
         * @dev ChainLink Price Fee contract.
         */
        AggregatorV3Interface priceFeed;
        /**
         * @notice Fees per operation in USD cents (2 decimal places)
         */
        mapping(bytes32 => uint256) feesUSDCents;
        /**
         * @dev Treasury account to collect fees.
         */
        address treasury;
        /**
         * @dev Authorized cross-chain senders.
         * Maps sender address to boolean indicating if authorized.
         */
        mapping(address => bool) crossChainSenders;
        /**
         * @dev Used to generate unique correlation Id even on the same block.
         */
        uint256 correlationNonce;
        /**
         * @dev List of supported target chain IDs for bridging.
         * Maps chain ID to a boolean indicating if it's supported.
         */
        mapping(string => bool) supportedTargetChains;
        /**
         * @dev Protocol admin management.
         * Maps admin address to boolean indicating if authorized as protocol admin.
         */
        mapping(address => bool) protocolAdmins;
        /**
         * @dev Forbidden subdomain labels.
         * Maps label hash (lowercase) to boolean indicating if forbidden.
         */
        mapping(bytes32 => bool) forbiddenLabels;
        /**
         * @dev Maps Registrar IANA ID to its capabilities.
         * These are the capabilities supported by a given registrar.
         */
        mapping(uint256 => uint256) registrarCapabilities;
        /**
         * @dev Address of a synthetic token contract.
         */
        SyntheticToken syntheticToken;
    }

    // Storage location for the ProxyDomaRecord storage
    bytes32 private constant _PROXY_DOMA_RECORD_STORAGE_LOCATION =
        keccak256(abi.encode(uint256(keccak256("doma.storage.ProxyDomaRecord")) - 1)) &
            ~bytes32(uint256(0xff));

    function proxyDomaRecordStorage() internal pure returns (ProxyDomaRecordStorage storage ds) {
        bytes32 position = _PROXY_DOMA_RECORD_STORAGE_LOCATION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            ds.slot := position
        }
    }

    /**
     * @notice `requestTokenization` operation key (for fee retrieval).
     */
    bytes32 public constant REQUEST_TOKENIZATION_OPERATION = keccak256("REQUEST_TOKENIZATION");

    /**
     * @notice `claimOwnership` operation key (for fee retrieval).
     */
    bytes32 public constant CLAIM_OWNERSHIP_OPERATION = keccak256("CLAIM_OWNERSHIP");

    /**
     * @notice `bridge` operation key (for fee retrieval).
     */
    bytes32 public constant BRIDGE_OPERATION = keccak256("BRIDGE");

    /**
     * @notice `setNameservers` operation key (for fee retrieval).
     */
    bytes32 public constant SET_NAMESERVERS_OPERATION = keccak256("SET_NAMESERVERS");

    /**
     * @notice `setDSKeys` operation key (for fee retrieval).
     */
    bytes32 public constant SET_DS_KEYS_OPERATION = keccak256("SET_DS_KEYS");

    /**
     * @notice `setDNSRRSet` operation key (for fee retrieval).
     */
    bytes32 public constant SET_DNS_RRSET_OPERATION = keccak256("SET_DNS_RRSET");

    /**
     * @notice `convertToSynthetic` operation key (for fee retrieval).
     */
    bytes32 public constant CONVERT_TO_SYNTHETIC_OPERATION = keccak256("CONVERT_TO_SYNTHETIC");

    /**
     * @notice `convertToOwnership` operation key (for fee retrieval).
     */
    bytes32 public constant CONVERT_TO_OWNERSHIP_OPERATION = keccak256("CONVERT_TO_OWNERSHIP");

    error ZeroAddress();
    error InvalidOwnerAddress(address owner, address tokenOwner);
    error InvalidOperation(bytes32 operation);
    error AccessControlNotAProtocolAdmin(address user);
    error ArrayLengthMismatch();
    error TransferLocked(uint256 tokenId);
    error NameTokenHasExpired(uint256 tokenId, uint256 expiresAt);
    error TokenAlreadySynthetic(uint256 tokenId);
    error TokenNotSynthetic(uint256 tokenId);
    error HasActiveSubdomains(uint256 tokenId, uint256 subdomainCount);
    error NotSubdomain(uint256 tokenId);
    error TokenNotRevocable(uint256 tokenId);
    error GroupRevoked(uint256 parentTokenId, uint256 groupId);
    error InsufficientCapabilities(uint256 tokenId, uint256 requiredCapability);

    function _useCorrelationId() internal returns (string memory) {
        ProxyDomaRecordStorage storage _storage = proxyDomaRecordStorage();
        uint256 correlationId = uint256(
            keccak256(abi.encodePacked(block.number, block.chainid, _storage.correlationNonce++))
        );
        return correlationId.toHexString();
    }

    function _relayMessage(
        bytes memory data,
        string memory correlationId,
        uint256 nonceKey
    ) internal {
        bytes[] memory attributes;

        if (bytes(correlationId).length == 0) {
            attributes = new bytes[](1);
        } else {
            attributes = new bytes[](2);
            attributes[1] = abi.encodeWithSelector(
                GatewayUtils.CORRELATION_ID_ATTRIBUTE,
                correlationId
            );
        }

        attributes[0] = abi.encodeWithSelector(GatewayUtils.NONCE_KEY_ATTRIBUTE, nonceKey);

        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();
        ds.crossChainGateway.sendMessage(
            ds.domaChainId,
            CAIPUtils.format(ds.domaChainId, ds.domaRecordAddress),
            data,
            attributes
        );
    }

    function _verifyTokenOwnership(uint256 tokenId, address owner) internal view {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();
        address tokenOwner;

        if (isSyntheticToken(tokenId)) {
            tokenOwner = ds.syntheticToken.ownerOf(tokenId);
        } else {
            tokenOwner = ds.ownershipToken.ownerOf(tokenId);
        }

        if (owner != tokenOwner) {
            revert InvalidOwnerAddress(owner, tokenOwner);
        }
    }

    function _isValidOperation(bytes32 operation) internal pure returns (bool) {
        return
            operation == REQUEST_TOKENIZATION_OPERATION ||
            operation == CLAIM_OWNERSHIP_OPERATION ||
            operation == BRIDGE_OPERATION ||
            operation == SET_NAMESERVERS_OPERATION ||
            operation == SET_DS_KEYS_OPERATION ||
            operation == SET_DNS_RRSET_OPERATION ||
            operation == CONVERT_TO_SYNTHETIC_OPERATION ||
            operation == CONVERT_TO_OWNERSHIP_OPERATION;
    }

    function _requireTokenExists(NameToken token, uint256 tokenId) internal view {
        if (!token.exists(tokenId)) {
            revert NameToken.NameTokenDoesNotExist(tokenId);
        }
    }

    function _burnOwnershipToken(uint256 tokenId, string memory correlationId) internal {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();
        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = tokenId;
        ds.ownershipToken.bulkBurn(tokenIds, correlationId);
    }

    /**
     * @notice Check if a token is a synthetic token.
     * @dev Checks ownership token contract first, then synthetic token contract.
     * Reverts with NameTokenDoesNotExist error if token doesn't exist in either contract.
     * @param tokenId The ID of the token to check.
     * @return True if the token is a synthetic token, false if it's an ownership token.
     */
    function isSyntheticToken(uint256 tokenId) internal view returns (bool) {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();

        // Check ownership token first
        if (ds.ownershipToken.exists(tokenId)) {
            return false;
        }

        // Check synthetic or ownership token exists
        if (!ds.syntheticToken.exists(tokenId)) {
            revert NameToken.NameTokenDoesNotExist(tokenId);
        }

        return true;
    }

    /**
     * @notice Get domain capabilities for a token (works for both ownership and synthetic tokens).
     * @dev Checks existence in contracts to determine which one to query.
     * @param tokenId The ID of the token.
     * @return The domain capabilities bitmask for the token.
     */
    function _getDomainCapabilities(uint256 tokenId) internal view returns (uint256) {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();

        if (isSyntheticToken(tokenId)) {
            return ds.syntheticToken.domainCapabilitiesOf(tokenId);
        } else {
            return ds.ownershipToken.domainCapabilitiesOf(tokenId);
        }
    }

    /**
     * @notice Validate a synthetic token for conversion to ownership token.
     * @dev Checks that token is synthetic, caller owns it, it's not locked, not expired, has no subdomains.
     * @param tokenId The ID of the token to validate.
     * @param owner The expected owner of the token.
     */
    function _validateSyntheticTokenForConversion(uint256 tokenId, address owner) internal view {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();

        if (!isSyntheticToken(tokenId)) {
            revert TokenNotSynthetic(tokenId);
        }

        _verifyTokenOwnership(tokenId, owner);

        bool isLocked = ds.syntheticToken.lockStatusOf(tokenId);
        if (isLocked) {
            revert TransferLocked(tokenId);
        }

        uint256 expiresAt = ds.syntheticToken.expirationOf(tokenId);
        if (expiresAt < block.timestamp) {
            revert NameTokenHasExpired(tokenId, expiresAt);
        }

        uint256 subdomainCount = ds.syntheticToken.subdomainCountOf(tokenId);
        if (subdomainCount > 0) {
            revert HasActiveSubdomains(tokenId, subdomainCount);
        }
    }

    /**
     * @notice Validate an ownership token for conversion to synthetic token.
     * @dev Checks that token is not synthetic, caller owns it, has DNS_RRSET capability, it's not locked, not expired.
     * @param tokenId The ID of the token to validate.
     * @param owner The expected owner of the token.
     */
    function _validateOwnershipTokenForConversion(uint256 tokenId, address owner) internal view {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();

        if (isSyntheticToken(tokenId)) {
            revert TokenAlreadySynthetic(tokenId);
        }

        _verifyTokenOwnership(tokenId, owner);

        uint256 capabilities = _getDomainCapabilities(tokenId);
        if (
            (capabilities & CAPABILITY_DNS_RECORDS_MANAGEMENT) != CAPABILITY_DNS_RECORDS_MANAGEMENT
        ) {
            revert InsufficientCapabilities(tokenId, CAPABILITY_DNS_RECORDS_MANAGEMENT);
        }

        bool isLocked = ds.ownershipToken.lockStatusOf(tokenId);
        if (isLocked) {
            revert TransferLocked(tokenId);
        }

        uint256 expiresAt = ds.ownershipToken.expirationOf(tokenId);
        if (expiresAt < block.timestamp) {
            revert NameTokenHasExpired(tokenId, expiresAt);
        }
    }

    /**
     * @notice Validates subdomain extraction parameters.
     * @dev Checks parent token state, host label format, and capability requirements.
     * @param parentTokenId The ID of the parent synthetic token.
     * @param host The subdomain host label.
     * @param capabilities The capability flags being requested.
     */
    function _validateSubdomainExtraction(
        uint256 parentTokenId,
        string calldata host,
        uint256 capabilities
    ) internal view {
        ProxyDomaRecordStorage storage ds = proxyDomaRecordStorage();

        // Validate parent token exists and is synthetic
        if (!isSyntheticToken(parentTokenId)) {
            revert TokenNotSynthetic(parentTokenId);
        }

        // Validate parent token is not locked
        bool isLocked = ds.syntheticToken.lockStatusOf(parentTokenId);
        if (isLocked) {
            revert TransferLocked(parentTokenId);
        }

        // Validate parent token is not expired
        uint256 parentExpiresAt = ds.syntheticToken.expirationOf(parentTokenId);
        if (parentExpiresAt < block.timestamp) {
            revert NameTokenHasExpired(parentTokenId, parentExpiresAt);
        }

        // Validate host label format
        NameUtils.ensureValidLabel(host);

        // Validate capability includes CAPABILITY_DNS_RECORDS_MANAGEMENT (4)
        if (
            (capabilities & CAPABILITY_DNS_RECORDS_MANAGEMENT) != CAPABILITY_DNS_RECORDS_MANAGEMENT
        ) {
            revert InsufficientCapabilities(parentTokenId, CAPABILITY_DNS_RECORDS_MANAGEMENT);
        }

        // Validate parent has the capabilities being delegated
        uint256 parentCapabilities = _getDomainCapabilities(parentTokenId);
        if ((parentCapabilities & capabilities) != capabilities) {
            revert InsufficientCapabilities(parentTokenId, capabilities);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { NameToken } from "./NameToken.sol";
import { DateUtils } from "./utils/DateUtils.sol";
import { NameUtils } from "./utils/NameUtils.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { ISyntheticToken } from "./interfaces/ISyntheticToken.sol";

/**
 * @title SyntheticToken
 * @notice Synthetic ownership tokens for subdomain/fractional ownership in DOMA protocol.
 * @dev Extends NameToken. Uses ERC-7201 namespaced storage to avoid collisions with inherited storage.
 */
contract SyntheticToken is NameToken, ISyntheticToken {
    // ============================================
    // Libraries
    // ============================================

    using EnumerableSet for EnumerableSet.UintSet;

    // ============================================
    // Types
    // ============================================

    /**
     * @dev Data structure for synthetic token-specific information.
     * Storage: 4 slots (parentTokenId: slot 0, host: slot 1, subdomainCount + revocable: slot 2, groupId: slot 3).
     * @param parentTokenId Token ID of the parent domain (0 for root domains).
     * @param host Subdomain host string (empty for root domains).
     * @param subdomainCount Number of active subdomains (uint64 allows 18 quintillion subdomains).
     * @param revocable Whether the synthetic token can be revoked by the parent owner.
     * @param groupId Group identifier for the token.
     */
    struct SyntheticData {
        uint256 parentTokenId;
        string host;
        uint64 subdomainCount;
        bool revocable;
        uint256 groupId;
    }

    /**
     * @dev Mint information structure for creating synthetic ownership tokens.
     * @param tokenId Unique identifier for the token.
     * @param registrarIanaId IANA ID of the sponsoring registrar.
     * @param owner Address of the token owner.
     * @param revocable Whether the token can be revoked.
     * @param sld Second-level domain label.
     * @param tld Top-level domain.
     * @param expiresAt Expiration timestamp.
     * @param capabilities Domain-level capability flags.
     * @param host Subdomain host (empty for root).
     * @param parentTokenId Parent token ID (0 for root).
     * @param groupId Group identifier for the token.
     */
    struct SyntheticTokenMintInfo {
        uint256 tokenId;
        uint256 registrarIanaId;
        address owner;
        bool revocable;
        string sld;
        string tld;
        uint256 expiresAt;
        uint256 capabilities;
        string host;
        uint256 parentTokenId;
        uint256 groupId;
    }

    /**
     * @dev Mint information structure for creating synthetic subdomain tokens without sld/tld.
     * @param receiver Address that will receive and own the token.
     * @param revocable Whether the token can be revoked.
     * @param expiresAt Expiration timestamp (0 for inheriting from parent).
     * @param capabilities Domain-level capability flags.
     * @param host Subdomain host label.
     * @param parentTokenId Parent token ID.
     * @param groupId Group identifier for the token.
     */
    struct SyntheticSubdomainMintInfo {
        address receiver;
        bool revocable;
        uint256 expiresAt;
        uint256 capabilities;
        string host;
        uint256 parentTokenId;
        uint256 groupId;
    }

    // ============================================
    // Storage (ERC-7201 Namespaced Storage Pattern)
    // ============================================

    /**
     * @dev Storage structure following ERC-7201 namespaced storage pattern.
     * This prevents storage collisions in upgradeable contracts.
     * @param syntheticData Maps token ID to synthetic-specific data.
     * @param currentTokenId Global counter for generating token IDs (added to baseTokenId).
     * @param subdomainTokenIds Maps namehash (baseTokenId) to set of active token IDs.
     */
    /// @custom:storage-location erc7201:doma.storage.SyntheticToken
    struct SyntheticTokenStorage {
        mapping(uint256 => SyntheticData) syntheticData;
        // group management mappings
        // ----------------------------
        // ParentId => GroupId => Count
        mapping(uint256 => mapping(uint256 => uint256)) groupCounts;
        // ParentId => GroupId => isRevoked
        mapping(uint256 => mapping(uint256 => bool)) isGroupRevoked;
        // subdomain minting
        // ----------------------------
        // Global counter added to baseTokenId for generating unique token IDs
        uint256 currentTokenId;
        // Namehash (baseTokenId) => Set of active TokenIds
        mapping(uint256 => EnumerableSet.UintSet) subdomainTokenIds;
        // Track last owner before token is burned (for claim after revocation)
        // TokenId => Last owner address
        mapping(uint256 => address) lastOwner;
    }

    /**
     * @dev Storage slot for SyntheticToken storage (ERC-7201).
     * keccak256(abi.encode(uint256(keccak256("doma.storage.SyntheticToken")) - 1)) & ~bytes32(uint256(0xff))
     */
    bytes32 private constant _SYNTHETIC_TOKEN_STORAGE_SLOT =
        keccak256(abi.encode(uint256(keccak256(bytes("doma.storage.SyntheticToken"))) - 1)) &
            ~bytes32(uint256(0xff));

    /**
     * @dev Group ID reserved for non-revocable tokens (group 0).
     * Non-revocable tokens must be assigned to this group and it cannot be revoked.
     */
    uint256 public constant NON_REVOCABLE_GROUP = 0;

    // ============================================
    // Errors
    // ============================================

    /**
     * @notice Thrown when attempting to transfer a token that belongs to a revoked group.
     * @param parentTokenId The parent token ID.
     * @param groupId The ID of the revoked group.
     */
    error GroupRevoked(uint256 parentTokenId, uint256 groupId);

    /**
     * @notice Thrown when caller is not the owner of the token.
     * @param caller The address of the caller.
     * @param tokenId The ID of the token.
     */
    error NotTokenOwner(address caller, uint256 tokenId);

    /**
     * @notice Thrown when attempting to renounce a root ownership token.
     * @param tokenId The ID of the token.
     */
    error CannotRenounceOwnershipToken(uint256 tokenId);

    /**
     * @notice Thrown when attempting to revoke a non-revocable token.
     * @param tokenId The ID of the token.
     */
    error TokenNotRevocable(uint256 tokenId);

    /**
     * @notice Thrown when subdomain expiration exceeds parent token expiration.
     * @param parentTokenId The parent token ID.
     * @param expiration The subdomain's expiration timestamp.
     * @param parentExpiration The parent token's expiration timestamp.
     */
    error ExpirationExceedsParent(
        uint256 parentTokenId,
        uint256 expiration,
        uint256 parentExpiration
    );

    /**
     * @notice Thrown when attempting to perform an operation on an expired token.
     * @param tokenId The ID of the expired token.
     * @param expiresAt The expiration timestamp of the token.
     */
    error NameTokenHasExpired(uint256 tokenId, uint256 expiresAt);

    /**
     * @notice Thrown when attempting to mint a new subdomain version while the previous version is still active.
     * @param previousTokenId The ID of the previous subdomain version that is still operational.
     */
    error SubdomainActive(uint256 previousTokenId);

    /**
     * @notice Thrown when attempting to renew a subdomain to an expiry not greater than current expiry.
     * @param tokenId The ID of the subdomain.
     * @param currentExpiry The current expiration timestamp.
     * @param newExpiry The attempted new expiration timestamp.
     */
    error RenewalMustExtendExpiry(uint256 tokenId, uint256 currentExpiry, uint256 newExpiry);

    /**
     * @notice Thrown when attempting to revoke group 0 (NON_REVOCABLE_GROUP).
     * @param groupId The group ID that cannot be revoked (always 0).
     */
    error CannotLockNonRevocableGroup(uint256 groupId);

    /**
     * @notice Thrown when a revocable token is assigned to NON_REVOCABLE_GROUP or vice versa.
     * @param parentTokenId The parent token ID.
     * @param groupId The invalid group ID.
     * @param revocable Whether the token is revocable.
     */
    error InvalidGroupAssignment(uint256 parentTokenId, uint256 groupId, bool revocable);

    /**
     * @notice Thrown when attempting to mint a synthetic subdomain token with a parent that is not an ownership token.
     * only 1 level deep to start with. We will update this in the future to support deeper hierarchies.
     * @param parentTokenId The ID of the parent token.
     */
    error ParentMustBeOwnershipToken(uint256 parentTokenId);

    /**
     * @notice Thrown when attempting to burn a token that doesn't meet burn criteria.
     * @param tokenId The ID of the token.
     */
    error CannotBurnToken(uint256 tokenId);

    // ============================================
    // Events
    // ============================================

    /**
     * @notice Emitted when a synthetic token is minted.
     * @param tokenId The ID of the minted token.
     * @param registrarIanaId IANA ID of the sponsoring registrar.
     * @param to Address receiving the token.
     * @param sld Second-level domain label.
     * @param tld Top-level domain.
     * @param expiresAt Expiration timestamp.
     * @param ownership Whether this is a root ownership token (parentTokenId == 0).
     * @param host Subdomain host string.
     * @param capabilities Domain-level capability flags.
     * @param revocable Whether the token can be revoked.
     * @param groupId Group identifier for the token.
     * @param correlationId Cross-chain operation correlation ID.
     */
    event SyntheticTokenMinted(
        uint256 indexed tokenId,
        uint256 registrarIanaId,
        address to,
        string sld,
        string tld,
        uint256 expiresAt,
        bool ownership,
        string host,
        uint256 capabilities,
        bool revocable,
        uint256 groupId,
        string correlationId
    );

    /**
     * @notice Emitted when a synthetic subdomain token is minted directly (without cross-chain call).
     * @param tokenId The ID of the minted subdomain token.
     * @param parentTokenId The ID of the parent token.
     * @param to Address receiving the token.
     * @param host Subdomain host label.
     * @param expiresAt Expiration timestamp.
     * @param capabilities Domain-level capability flags.
     * @param revocable Whether the token can be revoked.
     * @param groupId Group identifier for the token.
     * @param correlationId Correlation ID for tracking the operation.
     */
    event SyntheticSubdomainMinted(
        uint256 indexed tokenId,
        uint256 indexed parentTokenId,
        address to,
        string host,
        uint256 expiresAt,
        uint256 capabilities,
        bool revocable,
        uint256 groupId,
        string correlationId
    );

    /**
     * @notice Emitted when a group is revoked.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The ID of the group that was revoked.
     * @param groupCount The number of tokens in the revoked group.
     */
    event SyntheticGroupRevoked(
        uint256 indexed parentTokenId,
        uint256 indexed groupId,
        uint256 groupCount
    );

    /**
     * @notice Emitted when a synthetic subdomain token is renounced.
     * @param tokenId The ID of the renounced token.
     * @param owner The owner who renounced the token.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The ID of the group the token belonged to.
     */
    event SyntheticTokenRenounced(
        uint256 indexed tokenId,
        address indexed owner,
        uint256 indexed parentTokenId,
        uint256 groupId
    );

    /**
     * @notice Emitted when a synthetic subdomain token is revoked by parent owner.
     * @param tokenId The ID of the revoked token.
     * @param revokedFrom The address whose token was revoked.
     * @param revokedBy The parent token owner who revoked it.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The ID of the group the token belonged to.
     */
    event SyntheticTokenRevoked(
        uint256 indexed tokenId,
        address indexed revokedFrom,
        address indexed revokedBy,
        uint256 parentTokenId,
        uint256 groupId
    );

    function _getSyntheticTokenStorage()
        private
        pure
        returns (SyntheticTokenStorage storage syntheticStorage)
    {
        bytes32 slot = _SYNTHETIC_TOKEN_STORAGE_SLOT;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            syntheticStorage.slot := slot
        }
    }

    /**
     * @notice Mints a synthetic ownership token.
     * @dev Can only be called by authorized minter (ProxyDomaRecord). Emits SyntheticTokenMinted event.
     * @param info Synthetic token mint information including tokenId, registrar, owner and capabilities.
     * @param correlationId Correlation ID for tracking cross-chain operations.
     */
    function mintSyntheticFromOwnership(
        SyntheticTokenMintInfo calldata info,
        string calldata correlationId
    ) external onlyMinter {
        _mint(info.registrarIanaId, info.tokenId, info.expiresAt, info.owner);

        if (info.capabilities != 0) {
            _domainCapabilities[info.tokenId] = info.capabilities;
        }

        emit SyntheticTokenMinted(
            info.tokenId,
            info.registrarIanaId,
            info.owner,
            info.sld,
            info.tld,
            info.expiresAt,
            info.parentTokenId == 0,
            info.host,
            info.capabilities,
            info.revocable,
            info.groupId,
            correlationId
        );
    }

    /**
     * @notice Mints a synthetic subdomain token.
     * @dev Can only be called by authorized minter (ProxyDomaRecord). Stores synthetic data and emits event.
     *      If expiresAt is 0, stores 0 to indicate subdomain should inherit parent expiry dynamically.
     *      Token ID is generated from global counter.
     *      Uses SyntheticSubdomainMintInfo struct without sld/tld fields (inherited from parent).
     *      Emits SyntheticSubdomainMinted event.
     * @param info Synthetic subdomain mint information.
     * @param correlationId Correlation ID for tracking operations.
     * @return tokenId The generated token ID from global counter.
     */
    function mintSyntheticSubdomain(
        SyntheticSubdomainMintInfo calldata info,
        string calldata correlationId
    ) external onlyMinter returns (uint256) {
        // Validate host label
        NameUtils.ensureValidLabel(info.host);

        // Generate baseTokenId from parent + normalized host
        uint256 baseTokenId = NameUtils.namehash(info.parentTokenId, info.host);

        // Validate subdomain expiry doesn't exceed parent expiry
        uint256 parentExpiry = _getEffectiveExpiration(info.parentTokenId);
        if (info.expiresAt != 0) {
            _validateSubdomainExpiration(info.parentTokenId, info.expiresAt, parentExpiry);
        }

        // Get storage
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();

        // Validate group assignment rules
        // Revocable tokens cannot use group 0
        if (info.revocable && info.groupId == NON_REVOCABLE_GROUP) {
            revert InvalidGroupAssignment(info.parentTokenId, info.groupId, info.revocable);
        }

        // Non-revocable tokens must use group 0
        if (!info.revocable && info.groupId != NON_REVOCABLE_GROUP) {
            revert InvalidGroupAssignment(info.parentTokenId, info.groupId, info.revocable);
        }

        // Determine group assignment based on revocability
        uint256 assignedGroupId = info.revocable ? info.groupId : NON_REVOCABLE_GROUP;

        // Validate group is not revoked
        if (syntheticStorage.isGroupRevoked[info.parentTokenId][assignedGroupId]) {
            revert GroupRevoked(info.parentTokenId, assignedGroupId);
        }

        if (syntheticStorage.syntheticData[info.parentTokenId].parentTokenId != 0) {
            revert ParentMustBeOwnershipToken(info.parentTokenId);
        }

        // check for duplicates
        EnumerableSet.UintSet storage existingTokens = syntheticStorage.subdomainTokenIds[
            baseTokenId
        ];
        uint256[] memory tokenList = existingTokens.values();
        for (uint256 i = 0; i < tokenList.length; i++) {
            uint256 existingTokenId = tokenList[i];
            if (_isOperational(existingTokenId, syntheticStorage)) {
                // Found an operational subdomain - prevent duplicate
                // this can be changed in the future when we have other use cases
                revert SubdomainActive(existingTokenId);
            }
        }

        // Generate unique token ID: baseTokenId + global counter
        uint256 tokenId = baseTokenId + (++syntheticStorage.currentTokenId);

        // Add new token to the set
        existingTokens.add(tokenId);

        // Get registrarIanaId from parent token
        uint256 registrarIanaId = _registrarIanaIds[info.parentTokenId];

        // Mint without validation (already validated above)
        _safeMint(info.receiver, tokenId);
        _expirations[tokenId] = info.expiresAt;
        _registrarIanaIds[tokenId] = registrarIanaId;

        if (info.capabilities != 0) {
            _domainCapabilities[tokenId] = info.capabilities;
        }

        // Store synthetic data
        SyntheticData storage data = syntheticStorage.syntheticData[tokenId];
        data.parentTokenId = info.parentTokenId;
        data.host = info.host;
        data.revocable = info.revocable;
        data.subdomainCount = 0;
        data.groupId = assignedGroupId;

        // Increment group count and parent subdomain count
        unchecked {
            syntheticStorage.groupCounts[info.parentTokenId][assignedGroupId]++;
            syntheticStorage.syntheticData[info.parentTokenId].subdomainCount++;
        }

        emit SyntheticSubdomainMinted(
            tokenId,
            info.parentTokenId,
            info.receiver,
            info.host,
            info.expiresAt,
            info.capabilities,
            info.revocable,
            assignedGroupId,
            correlationId
        );

        return tokenId;
    }

    /**
     * @notice Renews a synthetic token by extending its expiration date.
     * @dev Can only be called by authorized minter (ProxyDomaRecord). Token must exist.
     * @param tokenId The ID of the token to renew.
     * @param expiresAt New expiration timestamp.
     * @param correlationId Correlation ID for tracking operations.
     */
    function renew(
        uint256 tokenId,
        uint256 expiresAt,
        string calldata correlationId
    ) external onlyMinter {
        DateUtils.validateExpirationDate(expiresAt);

        address currentOwner = _ownerOf(tokenId);
        if (currentOwner == address(0)) {
            revert NameTokenDoesNotExist(tokenId);
        }

        // Validate subdomain expiration doesn't exceed parent expiration
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        SyntheticData storage data = syntheticStorage.syntheticData[tokenId];

        if (data.parentTokenId != 0) {
            // Get effective current expiry (inherit from parent if not explicitly set)
            uint256 currentExpiry = _getEffectiveExpiration(tokenId);

            // Validate new expiry is greater than current expiry
            if (expiresAt <= currentExpiry) {
                revert RenewalMustExtendExpiry(tokenId, currentExpiry, expiresAt);
            }

            // Validate new expiry doesn't exceed parent expiry
            uint256 parentExpiry = _getEffectiveExpiration(data.parentTokenId);
            _validateSubdomainExpiration(data.parentTokenId, expiresAt, parentExpiry);
        }

        _expirations[tokenId] = expiresAt;

        emit NameTokenRenewed(tokenId, expiresAt, correlationId);
    }

    /// @notice Returns the current version of the contract
    /// @return The version string
    function version() external pure override returns (string memory) {
        return "1.0.0";
    }

    /**
     * @notice Get the expiration timestamp for a token, respecting parent expiry for subdomains.
     * @dev For subdomains, returns the minimum of own expiry and parent expiry.
     * @param tokenId The ID of the token.
     * @return The effective expiration timestamp.
     */
    function expirationOf(uint256 tokenId) external view override returns (uint256) {
        if (!_exists(tokenId)) {
            revert NameTokenDoesNotExist(tokenId);
        }

        return _getEffectiveExpiration(tokenId);
    }

    /**
     * @notice Get the subdomain count for a synthetic token.
     * @dev Returns the number of active subdomains for the given token.
     * @param tokenId The ID of the synthetic token.
     * @return The number of active subdomains.
     */
    function subdomainCountOf(uint256 tokenId) external view returns (uint256) {
        SyntheticTokenStorage storage _storage = _getSyntheticTokenStorage();
        return _storage.syntheticData[tokenId].subdomainCount;
    }

    /**
     * @notice Check if a group is revoked.
     * @dev Returns whether a specific group for a parent token is revoked.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The group identifier.
     * @return True if the group is revoked, false otherwise.
     */
    function isGroupRevoked(uint256 parentTokenId, uint256 groupId) external view returns (bool) {
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        return syntheticStorage.isGroupRevoked[parentTokenId][groupId];
    }

    /**
     * @notice Get the parent token ID for a subdomain.
     * @param tokenId The ID of the token.
     * @return The parent token ID (0 for root tokens).
     */
    function getParentTokenId(uint256 tokenId) external view returns (uint256) {
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        return syntheticStorage.syntheticData[tokenId].parentTokenId;
    }

    /**
     * @notice Get the full synthetic data for a token.
     * @param tokenId The ID of the token.
     * @return The complete synthetic data struct.
     */
    function getSyntheticData(uint256 tokenId) external view returns (SyntheticData memory) {
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        return syntheticStorage.syntheticData[tokenId];
    }

    /**
     * @notice Get the owner of a token, even if it has been burned.
     * @dev If the token exists, returns the current owner via ownerOf().
     *      If the token has been burned, returns the last owner stored before burning.
     *      Used to allow users to claim staked tokens after their subdomain NFT is revoked and burned.
     * @param tokenId The ID of the token.
     * @return The address of the current owner if token exists, or last owner if burned.
     */
    function lastOwnerOf(uint256 tokenId) external view returns (address) {
        if (_exists(tokenId)) {
            return ownerOf(tokenId);
        }
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        return syntheticStorage.lastOwner[tokenId];
    }

    /**
     * @notice Get all operational subdomain token IDs for a given parent and host.
     * @dev Computes the base token ID using namehash and returns only operational tokens.
     *      Returns empty array if no operational subdomains exist.
     *      Host is normalized to lowercase for case-insensitive matching (DNS standard).
     *      Filters out expired, burned, or group-revoked tokens.
     * @param parentTokenId The ID of the parent token.
     * @param host The subdomain host label.
     * @return Array of operational token IDs for this subdomain.
     */
    function getActiveSubdomainTokenIds(
        uint256 parentTokenId,
        string calldata host
    ) external view returns (uint256[] memory) {
        NameUtils.ensureValidLabel(host);

        // Compute base token ID from parent + normalized host
        uint256 baseTokenId = NameUtils.namehash(parentTokenId, host);

        // Get all tokens for this subdomain
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        uint256[] memory allTokens = syntheticStorage.subdomainTokenIds[baseTokenId].values();

        // First pass: count operational tokens
        uint256 operationalCount = 0;
        for (uint256 i = 0; i < allTokens.length; i++) {
            if (_isOperational(allTokens[i], syntheticStorage)) {
                operationalCount++;
            }
        }

        // Second pass: build array of operational tokens
        uint256[] memory operationalTokens = new uint256[](operationalCount);
        uint256 index = 0;
        for (uint256 i = 0; i < allTokens.length; i++) {
            if (_isOperational(allTokens[i], syntheticStorage)) {
                operationalTokens[index++] = allTokens[i];
            }
        }

        return operationalTokens;
    }

    /**
     * @notice Check if any subdomain is operational for a given parent and host.
     * @dev Returns true if at least one operational token exists in the set.
     * probably we pass in capabilities to check for specific operations in the future.
     * @param parentTokenId The ID of the parent token.
     * @param host The subdomain host label.
     * @return True if at least one subdomain token is operational, false otherwise.
     */
    function isSubdomainOperational(
        uint256 parentTokenId,
        string calldata host
    ) external view returns (bool) {
        NameUtils.ensureValidLabel(host);

        uint256 baseTokenId = NameUtils.namehash(parentTokenId, host);

        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        uint256[] memory tokens = syntheticStorage.subdomainTokenIds[baseTokenId].values();

        // Check if any token is operational
        for (uint256 i = 0; i < tokens.length; i++) {
            if (_isOperational(tokens[i], syntheticStorage)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @notice Revoke a group, preventing transfers of all tokens in that group.
     * @dev Can only be called by the minter. Reduces parent subdomain count.
     * @param parentTokenId The ID of the parent token.
     * @param groupId The group identifier to revoke.
     */
    function revokeGroup(uint256 parentTokenId, uint256 groupId) external onlyMinter {
        // Cannot revoke NON_REVOCABLE_GROUP
        if (groupId == NON_REVOCABLE_GROUP) {
            revert CannotLockNonRevocableGroup(groupId);
        }

        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();

        if (syntheticStorage.isGroupRevoked[parentTokenId][groupId]) {
            revert GroupRevoked(parentTokenId, groupId);
        }

        uint256 groupCount = syntheticStorage.groupCounts[parentTokenId][groupId];
        syntheticStorage.isGroupRevoked[parentTokenId][groupId] = true;

        syntheticStorage.syntheticData[parentTokenId].subdomainCount -= uint64(groupCount);

        emit SyntheticGroupRevoked(parentTokenId, groupId, groupCount);
    }

    /**
     * @notice Burn a synthetic token.
     * @dev For subdomain tokens, calls _burnSyntheticSubdomain to properly clean up parent relationships.
     *      For root tokens, performs standard burn cleanup.
     * @param tokenId The ID of the token to burn.
     * @param correlationId Correlation ID for tracking the burn operation.
     */
    function burnSynthetic(uint256 tokenId, string calldata correlationId) external onlyMinter {
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        SyntheticData storage data = syntheticStorage.syntheticData[tokenId];

        uint256 parentTokenId = data.parentTokenId;

        // For subdomain tokens, use specialized burn logic
        if (parentTokenId != 0) {
            uint256 groupId = data.groupId;
            _burnSyntheticSubdomain(tokenId, parentTokenId, groupId, correlationId);
        } else {
            // For root tokens (synthetic ownership tokens), use standard cleanup
            delete syntheticStorage.syntheticData[tokenId];
            _burnNameToken(tokenId, correlationId);
        }
    }

    /**
     * @notice Bulk burn expired or group-locked synthetic subdomain tokens.
     * @dev Can be called by anyone to burn tokens that are either expired or in a locked group.
     *      This helps reclaim storage and maintain accurate subdomain counts.
     *      Only works on subdomain tokens (parentTokenId != 0).
     *      Note: NON_REVOCABLE_GROUP cannot be locked, so group-locked tokens are always revocable.
     * @param tokenIds Array of token IDs to burn.
     * @param correlationId Correlation ID for tracking the bulk burn operation.
     */
    function bulkBurnInactiveSynthetic(
        uint256[] calldata tokenIds,
        string calldata correlationId
    ) external {
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();

        uint256 len = tokenIds.length;
        for (uint256 i = 0; i < len; ) {
            uint256 tokenId = tokenIds[i];

            SyntheticData storage data = syntheticStorage.syntheticData[tokenId];

            if (data.parentTokenId == 0) {
                revert CannotBurnToken(tokenId);
            }

            if (_isOperational(tokenId, syntheticStorage)) {
                revert CannotBurnToken(tokenId);
            }

            uint256 parentTokenId = data.parentTokenId;
            uint256 groupId = data.groupId;

            _burnSyntheticSubdomain(tokenId, parentTokenId, groupId, correlationId);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Check if a synthetic token is operational.
     * @dev Returns true if token exists, is not expired, and (for revocable subdomains) not in a locked group.
     *      Accepts pre-loaded storage to avoid repeated storage pointer lookups in loops.
     * @param tokenId The ID of the synthetic token.
     * @param syntheticStorage Pre-loaded storage pointer.
     * @return True if the token is operational, false otherwise.
     */
    function _isOperational(
        uint256 tokenId,
        SyntheticTokenStorage storage syntheticStorage
    ) internal view returns (bool) {
        if (!_exists(tokenId)) {
            return false;
        }

        uint256 effectiveExpiry = _getEffectiveExpiration(tokenId);
        if (effectiveExpiry < block.timestamp) {
            return false;
        }

        SyntheticData storage data = syntheticStorage.syntheticData[tokenId];

        if (data.parentTokenId != 0 && data.revocable) {
            if (syntheticStorage.isGroupRevoked[data.parentTokenId][data.groupId]) {
                return false;
            }
        }

        return true;
    }

    /**
     * @dev Get the effective expiration timestamp for a token, respecting parent expiry for subdomains.
     * @param tokenId The ID of the token.
     * @return The effective expiration timestamp.
     */
    function _getEffectiveExpiration(uint256 tokenId) internal view returns (uint256) {
        uint256 ownExpiry = _expirations[tokenId];

        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
        uint256 parentTokenId = syntheticStorage.syntheticData[tokenId].parentTokenId;

        if (parentTokenId == 0) {
            return ownExpiry;
        }

        uint256 parentExpiry = _expirations[parentTokenId];

        return ownExpiry == 0 ? parentExpiry : ownExpiry;
    }

    /**
     * @dev Override to check effective expiration for tokens with dynamic inheritance.
     * For subdomains with 0 expiry, this checks parent expiration instead of stored 0 value.
     */
    function _isTokenExpired(uint256 id) internal view override returns (bool) {
        return _getEffectiveExpiration(id) < block.timestamp;
    }

    /**
     * @dev Validates that a subdomain's expiration doesn't exceed its parent's expiration.
     * @param parentTokenId The parent token ID (used for error reporting)
     * @param expiresAt The proposed expiration for the subdomain
     * @param parentExpiry The parent token's expiration
     */
    function _validateSubdomainExpiration(
        uint256 parentTokenId,
        uint256 expiresAt,
        uint256 parentExpiry
    ) private pure {
        if (expiresAt > parentExpiry) {
            revert ExpirationExceedsParent(parentTokenId, expiresAt, parentExpiry);
        }
    }

    /**
     * @dev Internal function to burn a synthetic subdomain token and clean up all associated storage.
     * @param tokenId The ID of the token to burn.
     * @param parentTokenId The parent token ID.
     * @param groupId The group ID of the token.
     * @param correlationId Correlation ID for tracking the burn operation.
     */
    function _burnSyntheticSubdomain(
        uint256 tokenId,
        uint256 parentTokenId,
        uint256 groupId,
        string memory correlationId
    ) private {
        SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();

        // Remove token from subdomain set
        string memory host = syntheticStorage.syntheticData[tokenId].host;
        uint256 baseTokenId = NameUtils.namehash(parentTokenId, host);
        syntheticStorage.subdomainTokenIds[baseTokenId].remove(tokenId);

        syntheticStorage.groupCounts[parentTokenId][groupId]--;

        // Check group revocation status internally
        bool groupRevoked = syntheticStorage.isGroupRevoked[parentTokenId][groupId];

        // Only decrement subdomain count if group is not revoked
        if (!groupRevoked) {
            syntheticStorage.syntheticData[parentTokenId].subdomainCount--;
        }

        if (parentTokenId != 0) {
            address lastOwner = _ownerOf(tokenId);
            syntheticStorage.lastOwner[tokenId] = lastOwner;
        }

        delete syntheticStorage.syntheticData[tokenId];
        _burnNameToken(tokenId, correlationId);
    }

    /**
     * @dev Override to check if token belongs to a locked group before transfer.
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize
    ) internal override {
        super._beforeTokenTransfer(from, to, firstTokenId, batchSize);

        if (from != address(0) && to != address(0)) {
            SyntheticTokenStorage storage syntheticStorage = _getSyntheticTokenStorage();
            SyntheticData storage data = syntheticStorage.syntheticData[firstTokenId];

            if (
                data.parentTokenId != 0 &&
                syntheticStorage.isGroupRevoked[data.parentTokenId][data.groupId]
            ) {
                revert GroupRevoked(data.parentTokenId, data.groupId);
            }
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable-v4/access/AccessControlUpgradeable.sol";

/**
 * @dev This interface exists for Magic Eden collection ownership support.
 * It provides standard ownership functionality for collections on the Magic Eden marketplace.
 */
abstract contract AccessControlOwnable is AccessControlUpgradeable {
    error OwnerCannotBeZeroAddress();
    error NotSupported();
    error AtLeastOneAdminRequired();
    error OwnerMustHaveAdminRole(address owner);

    /**
     * @dev Emitted when ownership is transferred.
     */
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Storage structure for Ownable data.
     * Contains the owner address.
     */
    /// @custom:storage-location erc7201:doma.storage.Ownable
    struct OwnableStorage {
        address owner;
        address[] admins;
    }

    // Storage slot for Ownable storage
    bytes32 private constant _OWNABLE_STORAGE_SLOT =
        keccak256(abi.encode(uint256(keccak256(bytes("doma.storage.Ownable"))) - 1)) &
            ~bytes32(uint256(0xff));

    /// @notice Returns the address of the current owner
    /// @return The owner address
    function owner() public view virtual returns (address) {
        return _ownableStorage().owner;
    }

    /// @notice Renounces ownership (not supported - always reverts)
    /// @dev Kept for compatibility with OpenZeppelin's Ownable
    function renounceOwnership() public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        revert NotSupported();
    }

    /// @notice Transfers ownership to a new owner (admin role only)
    /// @dev New owner must have the DEFAULT_ADMIN_ROLE
    /// @param newOwner Address of the new owner
    function transferOwnership(address newOwner) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
        if (!hasRole(DEFAULT_ADMIN_ROLE, newOwner)) {
            revert OwnerMustHaveAdminRole(newOwner);
        }

        OwnableStorage storage s = _ownableStorage();
        emit OwnershipTransferred(s.owner, newOwner);
        s.owner = newOwner;
    }

    /**
     * @dev Returns the storage struct for Ownable
     */
    function _ownableStorage() internal pure returns (OwnableStorage storage storageStruct) {
        bytes32 slot = _OWNABLE_STORAGE_SLOT;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            storageStruct.slot := slot
        }
    }

    function _grantRole(bytes32 role, address account) internal virtual override {
        super._grantRole(role, account);

        if (role == DEFAULT_ADMIN_ROLE) {
            OwnableStorage storage s = _ownableStorage();

            if (s.owner == address(0)) {
                s.owner = account;
                emit OwnershipTransferred(address(0), account);
            }

            bool isAdminAlreadyExists = false;
            for (uint256 i = 0; i < s.admins.length; i++) {
                if (s.admins[i] == account) {
                    isAdminAlreadyExists = true;
                    break;
                }
            }

            if (!isAdminAlreadyExists) {
                s.admins.push(account);
            }
        }
    }

    function _revokeRole(bytes32 role, address account) internal virtual override {
        super._revokeRole(role, account);

        if (role == DEFAULT_ADMIN_ROLE) {
            OwnableStorage storage s = _ownableStorage();
            if (s.admins.length <= 1) {
                revert AtLeastOneAdminRequired();
            }

            for (uint256 i = 0; i < s.admins.length; i++) {
                if (s.admins[i] == account) {
                    // Move the last element into the place to delete and pop
                    s.admins[i] = s.admins[s.admins.length - 1];
                    s.admins.pop();

                    break;
                }
            }

            if (s.owner == account) {
                // If the owner is being revoked, set the first admin as the new owner
                address newAdmin = s.admins[0];
                s.owner = newAdmin;
                emit OwnershipTransferred(account, newAdmin);
            }
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

/**
 * Modified Bytes.sol from OpenZeppelin library
 * Original version cannot be used due to its reliance on `mcopy` instruction.
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Bytes.sol
 */
library ByteUtils {
    /**
     * @dev Backward search for `s` in `buffer` starting at position `pos`
     * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance
     * * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max
     *
     * NOTE: replicates the behavior of
     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`]
     */
    function lastIndexOf(
        bytes memory buffer,
        bytes1 s,
        uint256 pos
    ) internal pure returns (uint256) {
        unchecked {
            uint256 length = buffer.length;
            // NOTE here we cannot do `i = Math.min(pos + 1, length)` because `pos + 1` could overflow
            for (uint256 i = Math.min(pos, length - 1) + 1; i > 0; --i) {
                if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) {
                    return i - 1;
                }
            }
            return type(uint256).max;
        }
    }

    /**
     * @dev Reads a bytes32 from a bytes array without bounds checking.
     *
     * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
     * assembly block as such would prevent some optimizations.
     */
    function _unsafeReadBytesOffset(
        bytes memory buffer,
        uint256 offset
    ) private pure returns (bytes32 value) {
        // This is not memory safe in the general case, but all calls to this private function are within bounds.
        /* solhint-disable-next-line no-inline-assembly */
        assembly ("memory-safe") {
            value := mload(add(buffer, add(0x20, offset)))
        }
    }

    /**
     * @dev Taken from https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol#L228
     */
    function slice(
        bytes memory buffer,
        uint256 start,
        uint256 length
    ) internal pure returns (bytes memory) {
        /* solhint-disable-next-line custom-errors,gas-custom-errors */
        require(length + 31 >= length, "slice_overflow");
        /* solhint-disable-next-line custom-errors,gas-custom-errors */
        require(buffer.length >= start + length, "slice_outOfBounds");

        bytes memory tempBytes;

        /* 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)

                // The first word of the slice result is potentially a partial
                // word read from the original array. To read it, we calculate
                // the length of that partial word and start copying that many
                // bytes into the array. The first word we copy will start with
                // data we don't care about, but the last `lengthmod` bytes will
                // land at the beginning of the contents of the new array. When
                // we're done copying, we overwrite the full first word with
                // the actual length of the slice.
                let lengthmod := and(length, 31)

                // The multiplication in the next line is necessary
                // because when slicing multiples of 32 bytes (lengthmod == 0)
                // the following copy loop was copying the origin's length
                // and then ending prematurely not copying everything it should.
                let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
                let end := add(mc, length)

                for {
                    // The multiplication in the next line has the same exact purpose
                    // as the one above.
                    let cc := add(add(add(buffer, lengthmod), mul(0x20, iszero(lengthmod))), start)
                } lt(mc, end) {
                    mc := add(mc, 0x20)
                    cc := add(cc, 0x20)
                } {
                    mstore(mc, mload(cc))
                }

                mstore(tempBytes, length)

                //update free-memory pointer
                //allocating the array padded to 32 bytes like the compiler does now
                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)

                mstore(0x40, add(tempBytes, 0x20))
            }
        }

        return tempBytes;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ByteUtils } from "./ByteUtils.sol";

/**
 * Library for CAIP-2 and CAIP-10 support.
 * OpenZeppelin implementation cannot be used, since it requires 'cancun' evmVersion support
 * CAIP-2 source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/CAIP2.sol
 * CAIP-10 source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/CAIP10.sol
 */
library CAIPUtils {
    using Strings for address;
    using Strings for uint256;

    error LocalChainRequired(string caip2);

    /// @dev Return the CAIP-2 identifier for the current (local) chain.
    function caip2Local() internal view returns (string memory) {
        return format("eip155", block.chainid.toString());
    }

    /**
     * @dev Formats a CAIP identifier.
     */
    function format(
        string memory prefix,
        string memory value
    ) internal pure returns (string memory) {
        return string.concat(prefix, ":", value);
    }

    /**
     * @dev Parse CAIP 10 identifier into CAIP 2 and account address.
     */
    function parseCAIP10(
        string memory caip10
    ) internal pure returns (string memory caip2, string memory account) {
        bytes memory buffer = bytes(caip10);
        uint256 bufferLength = buffer.length;

        uint256 pos = ByteUtils.lastIndexOf(buffer, ":", type(uint256).max);
        return (
            string(ByteUtils.slice(buffer, 0, pos)),
            string(ByteUtils.slice(buffer, pos + 1, bufferLength - pos - 1))
        );
    }

    function parseLocalCAIP10(string memory caip10) internal view returns (address) {
        (string memory caip2, string memory accountString) = parseCAIP10(caip10);

        if (!Strings.equal(caip2, caip2Local())) {
            revert LocalChainRequired(caip2);
        }

        return Strings.parseAddress(accountString);
    }

    /**
     * @dev Parse CAIP-2 identifier to extract namespace.
     * For example: "eip155:1" returns "eip155", "solana:mainnet" returns "solana"
     */
    function parseCAIP2Namespace(string memory caip2) internal pure returns (string memory) {
        bytes memory buffer = bytes(caip2);

        // Find the first colon separator
        for (uint256 i = 0; i < buffer.length; i++) {
            if (buffer[i] == ":") {
                return string(ByteUtils.slice(buffer, 0, i));
            }
        }

        // If no colon found, return the entire string (shouldn't happen with valid CAIP-2)
        return caip2;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

library DateUtils {
    error PastExpirationDate(uint256 expirationDate);
    error TooBigExpirationDate(uint256 expirationDate, uint256 maxAllowedDate);

    /**
     * Used to perform a defensive check during mint & renewals.
     * Uses 366 days to account for potential leap years.
     */
    uint256 internal constant _TEN_YEARS = 10 * 366 days;

    function validateExpirationDate(uint256 expiresAt) internal view {
        if (expiresAt < block.timestamp) {
            revert PastExpirationDate(expiresAt);
        }

        uint256 maxAllowedDate = block.timestamp + _TEN_YEARS;
        if (expiresAt > maxAllowedDate) {
            revert TooBigExpirationDate(expiresAt, maxAllowedDate);
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import { ICreatorToken } from "../interfaces/ICreatorToken.sol";
import { ITransferValidator } from "../interfaces/ITransferValidator.sol";
import { ITransferValidatorSetTokenType } from "../interfaces/ITransferValidatorSetTokenType.sol";

abstract contract ERC721C is ICreatorToken {
    /**
     * @dev Storage structure for ERC721C
     */
    struct ERC721CStorage {
        // Address of the transfer validator contract
        address transferValidator;
        // Flag to automatically approve transfers from the validator
        bool autoApproveTransfersFromValidator;
    }

    // Storage slot for ERC721C storage
    bytes32 private constant _STORAGE_SLOT = keccak256("doma.storage.ERC721C");

    /**
     * @dev Returns the storage struct for ERC721C
     */
    function _erc721Storage() internal pure returns (ERC721CStorage storage storageStruct) {
        bytes32 slot = _STORAGE_SLOT;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            storageStruct.slot := slot
        }
    }
    // Token type constant for ERC721C
    uint16 private constant _TOKEN_TYPE_ERC721 = 1;
    /**
     * @dev Emitted when automatic approval of transfers from validator is updated
     */
    event AutomaticApprovalOfTransferValidatorSet(bool autoApproved);

    /**
     * @dev Thrown when setting a transfer validator address that has no deployed code
     */
    error InvalidTransferValidatorContract();

    /**
     * @notice Sets the transfer validator for the token contract (owner only)
     * @param validator The address of the transfer validator contract
     */
    function setTransferValidator(address validator) public virtual {
        _requireCallerIsContractOwner();

        bool isValidTransferValidator = validator.code.length > 0;

        if (validator != address(0) && !isValidTransferValidator) {
            revert InvalidTransferValidatorContract();
        }

        emit TransferValidatorUpdated(getTransferValidator(), validator);

        ERC721CStorage storage storage_ = _erc721Storage();
        storage_.transferValidator = validator;

        _registerTokenType(validator);
    }

    /**
     * @notice Returns the transfer validator contract address for this token contract
     * @return validator The address of the transfer validator
     */
    function getTransferValidator() public view virtual returns (address validator) {
        ERC721CStorage storage storage_ = _erc721Storage();
        validator = storage_.transferValidator;
    }

    /**
     * @notice Returns the function selector for the transfer validator's validation function
     * @return functionSignature The function selector
     * @return isViewFunction Whether the function is a view function
     */
    function getTransferValidationFunction()
        external
        pure
        virtual
        returns (bytes4 functionSignature, bool isViewFunction)
    {
        functionSignature = bytes4(keccak256("validateTransfer(address,address,address,uint256)"));
        isViewFunction = true;
    }

    /**
     * @notice Sets if the transfer validator is automatically approved as an operator for all token owners (owner only)
     * @param autoApprove If true, the collection's transfer validator will be automatically approved
     */
    function setAutomaticApprovalOfTransfersFromValidator(bool autoApprove) external virtual {
        _requireCallerIsContractOwner();
        ERC721CStorage storage storage_ = _erc721Storage();
        storage_.autoApproveTransfersFromValidator = autoApprove;
        emit AutomaticApprovalOfTransferValidatorSet(autoApprove);
    }

    /**
     * @notice Checks if automatic approval of transfers from validator is enabled
     * @return True if automatic approval is enabled
     */
    function getAutomaticApprovalOfTransfersFromValidator() external view virtual returns (bool) {
        return _erc721Storage().autoApproveTransfersFromValidator;
    }

    /**
     * @dev Pre-validates a token transfer, reverting if the transfer is not allowed by this token's security policy
     */
    function _preValidateTransfer(
        address caller,
        address from,
        address to,
        uint256 tokenId,
        uint256
    ) internal virtual {
        address validator = getTransferValidator();

        if (validator != address(0)) {
            if (msg.sender == validator) {
                return;
            }

            ITransferValidator(validator).validateTransfer(caller, from, to, tokenId);
        }
    }

    /**
     * @dev Returns the token type for this contract (ERC721)
     */
    function _tokenType() internal pure virtual returns (uint16) {
        return _TOKEN_TYPE_ERC721;
    }

    /**
     * @dev Registers the token type with the transfer validator
     */
    /* solhint-disable no-empty-blocks */
    function _registerTokenType(address validator) internal virtual {
        if (validator != address(0)) {
            uint256 validatorCodeSize;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                validatorCodeSize := extcodesize(validator)
            }
            if (validatorCodeSize > 0) {
                try
                    ITransferValidatorSetTokenType(validator).setTokenTypeOfCollection(
                        address(this),
                        _tokenType()
                    )
                {} catch {}
            }
        }
    }

    /**
     * @dev Checks if the caller is the contract owner
     */
    function _requireCallerIsContractOwner() internal virtual;
}

File 59 of 60 : GatewayUtils.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

library GatewayUtils {
    bytes4 internal constant NONCE_ATTRIBUTE = bytes4(keccak256("nonce(uint64)"));

    bytes4 internal constant CORRELATION_ID_ATTRIBUTE = bytes4(keccak256("correlationId(bytes32)"));

    bytes4 internal constant NONCE_KEY_ATTRIBUTE = bytes4(keccak256("nonceKey(uint256)"));

    error PayableCallsUnsupported();
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

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

library NameUtils {
    error InvalidLabel(string label);
    error InvalidName(string name);
    error InvalidHost(string host);

    function ensureValidLabel(string memory label) internal pure {
        if (!isValidLabel(label)) {
            revert InvalidLabel(label);
        }
    }

    function ensureValidName(string memory name) internal pure {
        if (!isValidName(name)) {
            revert InvalidName(name);
        }
    }

    function ensureValidHost(string memory host) internal pure {
        if (!isValidHost(host)) {
            revert InvalidHost(host);
        }
    }

    function isValidName(string memory name) internal pure returns (bool) {
        return _validateDotSeparated(name, false);
    }

    function isValidLabel(string memory label) internal pure returns (bool) {
        return _isValidLabelInternal(label, false);
    }

    /**
     * @dev Validates a DNS host per RFC 2181 and RFC 8552.
     * Allows underscores for special DNS names like _acme-challenge, _dmarc, etc.
     * Supports multi-level hosts like "_api.hostname.example".
     */
    function isValidHost(string memory host) internal pure returns (bool) {
        return _validateDotSeparated(host, true);
    }

    /**
     * @dev Validates a single DNS host label allowing underscores.
     * Used by isValidHost to validate each dot-separated part.
     */
    function isValidHostLabel(string memory label) internal pure returns (bool) {
        return _isValidLabelInternal(label, true);
    }

    function _validateDotSeparated(
        string memory input,
        bool allowUnderscore
    ) private pure returns (bool) {
        bytes memory b = bytes(input);
        uint256 len = b.length;

        // Check length (1-253 characters, max domain name length in DNS)
        if (len == 0 || len > 253) {
            return false;
        }

        // Ensure no leading or trailing dots
        if (b[0] == 0x2E || b[len - 1] == 0x2E) {
            // ASCII '.' = 0x2E
            return false;
        }

        // Split into labels and validate each one
        string memory currentLabel;
        uint256 start = 0;

        for (uint256 i = 0; i < len; i++) {
            if (b[i] == 0x2E) {
                // If character is '.'
                if (i == start) {
                    // Empty label (double dot case like "example..com")
                    return false;
                }

                // Extract the label and validate it
                currentLabel = substring(input, start, i);
                if (!_isValidLabelInternal(currentLabel, allowUnderscore)) {
                    return false;
                }

                start = i + 1;
            }
        }

        // Validate the last label (after the last dot)
        currentLabel = substring(input, start, len);
        if (!_isValidLabelInternal(currentLabel, allowUnderscore)) {
            return false;
        }

        return true;
    }

    function _isValidLabelInternal(
        string memory label,
        bool allowUnderscore
    ) private pure returns (bool) {
        bytes memory b = bytes(label);
        uint256 len = b.length;

        // Check length (1-63 characters)
        if (len == 0 || len > 63) {
            return false;
        }

        // Check if it's a Punycode label (starts with "xn--")
        bool isPunycode = (len > 4 &&
            b[0] == 0x78 && // 'x'
            b[1] == 0x6E && // 'n'
            b[2] == 0x2D && // '-'
            b[3] == 0x2D); // '-'

        // Validate characters and hyphen placement rules
        for (uint256 i = 0; i < len; i++) {
            bytes1 char = b[i];

            bool isValidChar = (char >= 0x61 && char <= 0x7A) || // a-z
                (char >= 0x30 && char <= 0x39) || // 0-9
                (char == 0x2D) || // '-' hyphen
                (allowUnderscore && char == 0x5F); // '_' underscore (if allowed)

            if (!isValidChar) {
                return false;
            }

            // Ensure no hyphen at start or end (underscore is allowed at start)
            if ((i == 0 || i == len - 1) && char == 0x2D) {
                return false;
            }

            // Check for double hyphen (`--`) at positions 3 and 4 (unless "xn--")
            if (i == 2 && char == 0x2D && i + 1 < len && b[i + 1] == 0x2D) {
                if (!isPunycode) {
                    return false; // Double hyphen not allowed in 3rd and 4th position unless "xn--"
                }
            }
        }

        return true;
    }

    /**
     * @dev Computes a full name namehash, e.g. `example.com`
     */
    function namehash(string memory name) internal pure returns (uint256) {
        bytes memory nameBytes = bytes(name);

        uint256 nameHash = 0;
        uint256 lastDotPosition = nameBytes.length;

        while (lastDotPosition > 0) {
            uint256 labelStart = lastDotPosition - 1;

            // Find the start of the label
            while (labelStart > 0 && nameBytes[labelStart - 1] != ".") {
                labelStart--;
            }
            uint256 labelLength = lastDotPosition - labelStart;

            string memory label = string(ByteUtils.slice(nameBytes, labelStart, labelLength));

            nameHash = namehash(nameHash, label);

            if (labelStart == 0) {
                break;
            } else {
                lastDotPosition = labelStart - 1;
            }
        }

        return nameHash;
    }

    function namehash(uint256 parent, string memory label) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked(parent, keccak256(abi.encodePacked(label)))));
    }

    function substring(
        string memory str,
        uint256 startIndex,
        uint256 endIndex
    ) private pure returns (string memory) {
        bytes memory strBytes = bytes(str);
        bytes memory result = new bytes(endIndex - startIndex);
        for (uint256 i = startIndex; i < endIndex; i++) {
            result[i - startIndex] = strBytes[i];
        }
        return string(result);
    }
}

Settings
{
  "evmVersion": "istanbul",
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "viaIR": true,
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[{"internalType":"bytes32","name":"operation","type":"bytes32"},{"internalType":"uint256","name":"nameCount","type":"uint256"}],"name":"FeeNotRequired","type":"error"},{"inputs":[{"internalType":"uint256","name":"parentTokenId","type":"uint256"},{"internalType":"uint256","name":"groupId","type":"uint256"}],"name":"GroupRevoked","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"subdomainCount","type":"uint256"}],"name":"HasActiveSubdomains","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"requiredCapability","type":"uint256"}],"name":"InsufficientCapabilities","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFee","type":"uint256"},{"internalType":"uint256","name":"providedFee","type":"uint256"}],"name":"InvalidFee","type":"error"},{"inputs":[{"internalType":"string","name":"host","type":"string"}],"name":"InvalidHost","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[{"internalType":"string","name":"label","type":"string"}],"name":"InvalidLabel","type":"error"},{"inputs":[{"internalType":"string","name":"name","type":"string"}],"name":"InvalidName","type":"error"},{"inputs":[{"internalType":"bytes32","name":"operation","type":"bytes32"}],"name":"InvalidOperation","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"tokenOwner","type":"address"}],"name":"InvalidOwnerAddress","type":"error"},{"inputs":[{"internalType":"int256","name":"price","type":"int256"}],"name":"InvalidPrice","type":"error"},{"inputs":[{"internalType":"uint256","name":"ianaId","type":"uint256"},{"internalType":"uint256","name":"expectedIanaId","type":"uint256"}],"name":"InvalidRegistrar","type":"error"},{"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"InvalidSigner","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"InvalidTokenId","type":"error"},{"inputs":[{"internalType":"string","name":"sld","type":"string"},{"internalType":"string","name":"tld","type":"string"}],"name":"NameAlreadyTokenized","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"NameTokenDoesNotExist","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"}],"name":"NameTokenHasExpired","type":"error"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"NonceAlreadyUsed","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"NotSubdomain","type":"error"},{"inputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"uint256","name":"updatedAt","type":"uint256"}],"name":"PriceFeedStalePrice","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"StringsInsufficientHexLength","type":"error"},{"inputs":[{"internalType":"string","name":"host","type":"string"}],"name":"SubdomainHostInUse","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"SyntheticTokenNotSupported","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"TokenAlreadySynthetic","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"TokenNotRevocable","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"TokenNotSynthetic","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"TransferLocked","type":"error"},{"inputs":[{"internalType":"string","name":"chainId","type":"string"}],"name":"UnsupportedTargetChain","type":"error"},{"inputs":[{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"currentTime","type":"uint256"}],"name":"VoucherExpired","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"feeWei","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeUsdCents","type":"uint256"},{"indexed":false,"internalType":"string","name":"correlationId","type":"string"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"parentTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"groupId","type":"uint256"}],"name":"SyntheticTokenRenounced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"revokedFrom","type":"address"},{"indexed":true,"internalType":"address","name":"revokedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"parentTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"groupId","type":"uint256"}],"name":"SyntheticTokenRevoked","type":"event"},{"inputs":[],"name":"BRIDGE_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"CLAIM_OWNERSHIP_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"CONVERT_TO_OWNERSHIP_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"CONVERT_TO_SYNTHETIC_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"REQUEST_TOKENIZATION_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"SET_DNS_RRSET_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"SET_DS_KEYS_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"SET_NAMESERVERS_OPERATION","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"targetChainId","type":"string"},{"internalType":"string","name":"targetOwnerAddress","type":"string"}],"name":"bridge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"targetChainId","type":"string"},{"internalType":"string","name":"targetOwnerAddress","type":"string"}],"name":"bridge","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"components":[{"internalType":"uint256","name":"registrantHandle","type":"uint256"},{"internalType":"enum IDomaRecord.ProofOfContactsSource","name":"proofSource","type":"uint8"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"}],"internalType":"struct ProxyDomaRecordUserFacet.ProofOfContactsVoucher","name":"proofOfContactsVoucher","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"claimOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"components":[{"internalType":"uint256","name":"registrantHandle","type":"uint256"},{"internalType":"enum IDomaRecord.ProofOfContactsSource","name":"proofSource","type":"uint8"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"}],"internalType":"struct ProxyDomaRecordUserFacet.ProofOfContactsVoucher","name":"proofOfContactsVoucher","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"claimOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"convertToOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"convertToSynthetic","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"domainVersion","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"operation","type":"bytes32"}],"name":"feesUSDCents","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"feeUSDCents","type":"uint256"}],"name":"getNativePrice","outputs":[{"internalType":"uint256","name":"nativeFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"operation","type":"bytes32"}],"name":"getOperationFeeInNative","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainId","type":"string"}],"name":"isTargetChainSupported","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentTokenId","type":"uint256"},{"internalType":"string","name":"host","type":"string"},{"internalType":"uint256","name":"capabilities","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"bool","name":"revocable","type":"bool"},{"internalType":"uint256","name":"groupId","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mintSyntheticSubdomain","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"}],"name":"renewSubdomain","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"renounceSynthetic","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"requestDetokenization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"requestDetokenization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"string","name":"sld","type":"string"},{"internalType":"string","name":"tld","type":"string"}],"internalType":"struct IDomaRecord.NameInfo[]","name":"names","type":"tuple[]"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"address","name":"ownerAddress","type":"address"}],"internalType":"struct ProxyDomaRecordUserFacet.TokenizationVoucher","name":"voucher","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"requestTokenization","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentTokenId","type":"uint256"},{"internalType":"uint256","name":"groupId","type":"uint256"}],"name":"revokeGroup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"revokeSynthetic","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"host","type":"string"},{"internalType":"string","name":"recordType","type":"string"},{"internalType":"uint32","name":"ttl","type":"uint32"},{"internalType":"string[]","name":"records","type":"string[]"},{"internalType":"bool","name":"","type":"bool"}],"name":"setDNSRRSet","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"host","type":"string"},{"internalType":"string","name":"recordType","type":"string"},{"internalType":"uint32","name":"ttl","type":"uint32"},{"internalType":"string[]","name":"records","type":"string[]"}],"name":"setDNSRRSet","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"components":[{"internalType":"uint32","name":"keyTag","type":"uint32"},{"internalType":"uint8","name":"algorithm","type":"uint8"},{"internalType":"uint8","name":"digestType","type":"uint8"},{"internalType":"bytes","name":"digest","type":"bytes"}],"internalType":"struct IDomaRecord.DSKey[]","name":"dsKeys","type":"tuple[]"}],"name":"setDSKeys","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"components":[{"internalType":"uint32","name":"keyTag","type":"uint32"},{"internalType":"uint8","name":"algorithm","type":"uint8"},{"internalType":"uint8","name":"digestType","type":"uint8"},{"internalType":"bytes","name":"digest","type":"bytes"}],"internalType":"struct IDomaRecord.DSKey[]","name":"dsKeys","type":"tuple[]"},{"internalType":"bool","name":"","type":"bool"}],"name":"setDSKeys","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string[]","name":"nameservers","type":"string[]"},{"internalType":"bool","name":"","type":"bool"}],"name":"setNameservers","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string[]","name":"nameservers","type":"string[]"}],"name":"setNameservers","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"}]

60808060405234601557615624908161001b8239f35b600080fdfe6080604052600436101561001257600080fd5b60003560e01c80630cbc4e93146102375780630e160be6146102325780632f945faa1461022d57806335abf04a146102285780633761014014610223578063379006ba1461021e5780633827aaaf1461021957806340fb6b8e1461021457806354fd4d501461020f578063575c472d1461020a578063628c19061461020557806373d9d085146102005780637f13e412146101fb5780638141f9ed146101f657806384b0196e146101f157806386d45941146101ec578063870a3d84146101e7578063989082f9146101e25780639c7d2ab7146101dd578063a28df472146101d8578063aa00c548146101d3578063ade81788146101ce578063b36210c1146101c9578063ba26a79d146101c4578063bce69e8c146101bf578063c4851c0c146101ba578063de174837146101b5578063e89174bd146101b0578063e952a25a146101ab578063ea0b1829146101a6578063eabd785c146101a1578063eb7ce2491461019c578063efd0c5a6146101975763efea996a1461019257600080fd5b611c0d565b611be4565b611b16565b611a42565b61193d565b611914565b6118dc565b611797565b61175d565b61158a565b61131d565b611304565b6112f0565b6112c7565b6112af565b61126a565b6111e8565b61116b565b611142565b6110c2565b610e0f565b610cc3565b610ae6565b610a52565b610a05565b6109ba565b610800565b6105c4565b6104f9565b610497565b610327565b61029b565b610275565b61024c565b600091031261024757565b600080fd5b3461024757600036600319011261024757602060405160008051602061550f8339815191528152f35b34610247576020366003190112610247576020610293600435611d85565b604051908152f35b3461024757600036600319011261024757602060405160008051602061556f8339815191528152f35b9181601f84011215610247578235916001600160401b038311610247576020808501948460051b01011161024757565b9060406003198301126102475760043591602435906001600160401b03821161024757610323916004016102c4565b9091565b610330366102f4565b61033b839293612b8c565b610470579161034a3383612c8b565b61035382612da3565b61035b612f0d565b9061036582612fdd565b60405190637992685160e11b60208301528460848301856024850152606060448501525260a4820160a48660051b8401019582600090607e1981360301935b8383106103e157508589036023190160648701526103df8888886103da816103cc8f85610984565b03601f198101835282611d0f565b613860565b005b90919293949860a31987820301825289358681121561024757602061046060019360806104508885960163ffffffff61041982610a41565b16845260ff8682013561042b81611e17565b168685015260ff604082013561044081611e17565b1660408501526060810190611e22565b9190928160608201520191611e53565b9b019201930191909493926103a4565b5063d08fd27160e01b60005260045260246000fd5b60209060031901126102475760043590565b346102475760206102936104aa36610485565b611e74565b8015150361024757565b9060606003198301126102475760043591602435906001600160401b038211610247576104e8916004016102c4565b90916044356104f6816104af565b90565b610502366104b9565b5061050e839293612b8c565b6104705761051c3383612c8b565b61052582612b8c565b610470576103df926105373384612c8b565b61054083612da3565b6103da61054b612f0d565b9261055584613229565b6103cc610585604051948593639f63ae2360e01b6020860152886024860152606060448601526084850191612301565b82810360231901606484015285610984565b9181601f84011215610247578235916001600160401b038311610247576020838186019501011161024757565b6060366003190112610247576004356024356001600160401b038111610247576105f2903690600401610597565b91906044356001600160401b03811161024757610613903690600401610597565b61061f84939293612b8c565b6107eb5761062d3385612c8b565b61063e61063a8684612653565b1590565b6107cc5761064a612f0d565b92610654846132ac565b61067e610672600461066461295b565b01546001600160a01b031690565b6001600160a01b031690565b6040516364f1992360e01b81526004810187905290602090829060249082905afa9081156107825760009161079d575b50610787576106c3610672600461066461295b565b604051633329dc9d60e11b81526004810187905290602090829060249082905afa90811561078257600091610753575b504281106107395750946103cc846103da936103df98610713838a6139a8565b61071c89613a7e565b60405163069322e360e51b60208201529788969160248801611ed1565b636581bb3f60e11b600052600486905260245260445b6000fd5b610775915060203d60201161077b575b61076d8183611d0f565b810190611ec2565b386106f3565b503d610763565b611d79565b63a9de8c6f60e01b600052600485905260246000fd5b6107bf915060203d6020116107c5575b6107b78183611d0f565b810190611ead565b386106ae565b503d6107ad565b604051620d153960e01b8152806107e7878560048401611e8f565b0390fd5b8363d08fd27160e01b60005260045260246000fd5b346102475760403660031901126102475760043561081f6024356104af565b61082881612b8c565b610901576108363382612c8b565b61083f81612b8c565b610901576103df906108513382612c8b565b610859612f0d565b6108ae6103da61086884613a7e565b6103cc8461087546613a7e565b6108dd600160409260266020855161088d8782611d0f565b6006815201916565697031353560d01b835285519a8b936020850190610915565b601d60f91b828401526108cb815180926020602787019101610961565b8201010301601f198101885287611d0f565b6108e633614173565b9051631acdf90760e21b602082015295869460248601612795565b63d08fd27160e01b60005260045260246000fd5b60005b6006811061092b57505060066000910152565b8181015183820152602001610918565b60005b6062811061095157505060626000910152565b818101518382015260200161093e565b60005b8381106109745750506000910152565b8181015183820152602001610964565b9060209161099d81518092818552858086019101610961565b601f01601f1916010190565b9060206104f6928181520190610984565b3461024757600036600319011261024757610a0160408051906109dd8183611d0f565b60058252640312e312e360dc1b602083015251918291602083526020830190610984565b0390f35b3461024757600036600319011261024757602060405160008051602061552f8339815191528152f35b6064359063ffffffff8216820361024757565b359063ffffffff8216820361024757565b60c0366003190112610247576004356024356001600160401b03811161024757610a80903690600401610597565b6044929192356001600160401b03811161024757610aa2903690600401610597565b610aad949194610a2e565b90608435956001600160401b03871161024757610ad16103df9736906004016102c4565b95909460a43597610ae1896104af565b611f46565b3461024757602036600319011261024757600435610b043382612c8b565b6012610b0e61295b565b018054909190610b26906001600160a01b0316610672565b6040516316740b0b60e31b8152600481018390529092600082602481875afa91821561078257600092610ca0575b50815115610c8a578151608083018051604051633d854fa160e11b81526004810193909352602483015294602090829060449082905afa90811561078257600091610c6b575b50610c4d57610bbb610672610bad612f0d565b92546001600160a01b031690565b803b156102475760405163576bd72160e01b8152916000918391829084908290610be9908a60048401612033565b03925af1801561078257610c32575b505191516040519081523391907f5489bf68a3ad4b454ddf44e2cc0863dd86a8a874fcf2191c8818b6908bd1a4eb9080602081015b0390a4005b80610c416000610c4793611d0f565b8061023c565b38610bf8565b815184516314f241a760e01b60005260049190915260245260446000fd5b610c84915060203d6020116107c5576107b78183611d0f565b38610b9a565b632db04fed60e21b600052600483905260246000fd5b610cbc91923d8091833e610cb48183611d0f565b810190611f6e565b9038610b54565b3461024757604036600319011261024757602435600435610ce43382612c8b565b610cec61295b565b610cf861063a83612b8c565b610dc95760120154610d12906001600160a01b0316610672565b60405163cfa651f360e01b815260048101839052909290602081602481875afa90811561078257600091610daa575b50610d9457823b1561024757604051633f89f20960e11b815260048101929092526024820152906000908290818381604481015b03925af1801561078257610d8557005b80610c4160006103df93611d0f565b63ed15e6cf60e01b600052600482905260246000fd5b610dc3915060203d60201161077b5761076d8183611d0f565b38610d41565b635a55e97160e11b600052600482905260246000fd5b608090604319011261024757604490565b608090602319011261024757602490565b908160809103126102475790565b6040366003190112610247576004356001600160401b03811161024757610e3a903690600401610e01565b6024356001600160401b03811161024757610e59903690600401610597565b9190610e686040830135613bd6565b610e756020830135613bf8565b6060820191610e866106728461204a565b330361106257610ec5610e998280612054565b959050610ea4612f0d565b93610eaf858861332f565b610ec0610ebb85613de7565b613ef4565b613f76565b9360005b818110610f5e5750506103df93610f3083610ee784610f3e95612054565b9096610f13610f0e610f08610efb46613a7e565b610f0361414f565b614a73565b9261204a565b614173565b9160405198899663b5999e7960e01b6020890152602488016121c2565b03601f198101845283611d0f565b6040516020810190610f55816103cc4387866122a2565b51902091613860565b610f7a610f7582610f6f8680612054565b9061209f565b612118565b610f848151613fe7565b6020810190610f93825161401d565b6110026020610fd2610fba610fcd85516103cc8851610fc760405195869489860190612186565b601760f91b815260010190565b90612186565b6140a1565b610fe2610672600461066461295b565b6040518080958194634f558e7960e01b8352600483019190602083019252565b03915afa90811561078257600091611044575b50611024575050600101610ec9565b519051604051630e3ba82b60e31b81529182916107e7916004840161219d565b61105c915060203d81116107c5576107b78183611d0f565b38611015565b61074f61106e8461204a565b63adabb9d960e01b600052336004526001600160a01b0316602452604490565b906020808351928381520192019060005b8181106110ac5750505090565b825184526020938401939092019160010161109f565b34610247576000366003190112610247576111136110de6142b2565b610a016110e96142d4565b6111216110f46122d7565b91604051958695600f60f81b875260e0602088015260e0870190610984565b908582036040870152610984565b90466060850152306080850152600060a085015283820360c085015261108e565b3461024757600036600319011261024757602060405160008051602061554f8339815191528152f35b6080366003190112610247576004356111856024356104af565b6044356001600160401b038111610247576111a4903690600401610597565b91906064356001600160401b038111610247576111c5903690600401610597565b6111d184939293612b8c565b6107eb576111df3385612c8b565b61061f84612b8c565b60a0366003190112610247576004356024356001600160401b03811161024757611216903690600401610597565b916044356001600160401b03811161024757611236903690600401610597565b61123e610a2e565b91608435956001600160401b038711610247576112626103df9736906004016102c4565b9690956123be565b34610247576020366003190112610247576004356001600160401b038111610247576112a561129f6020923690600401610597565b90612653565b6040519015158152f35b346102475760206102936112c236610485565b61267b565b346102475760003660031901126102475760206040516000805160206155af8339815191528152f35b6112f9366102f4565b610525839293612b8c565b3461024757602036600319011261024757600435610836565b3461024757602036600319011261024757600435601261133b61295b565b018054611350906001600160a01b0316610672565b6040516316740b0b60e31b81526004810184905290929091600083602481875afa9283156107825760009361156f575b508251156115595761139483513390612c8b565b6113a461063a6060850151151590565b611543578251608084018051604051633d854fa160e11b8152600481019390935260248301529390602081604481895afa90811561078257600091611524575b50611507576040516331a9108f60e11b81526004810184905294602090869060249082905afa948515610782576000956114d6575b50611436610672611428612f0d565b93546001600160a01b031690565b803b156102475760405163576bd72160e01b8152926000918491829084908290611464908a60048401612033565b03925af1908115610782577f676dac521fa16607d417251e9ab50656928e72849236f2c82525fdbfa23d2cd492610c2d926114c1575b5051935160408051958652602086019190915233956001600160a01b031694918291820190565b80610c4160006114d093611d0f565b3861149a565b6114f991955060203d602011611500575b6114f18183611d0f565b8101906127cf565b9338611419565b503d6114e7565b5183516314f241a760e01b60005260049190915260245260446000fd5b61153d915060203d6020116107c5576107b78183611d0f565b386113e4565b63117c7f6560e01b600052600482905260246000fd5b632db04fed60e21b600052600482905260246000fd5b61158391933d8091833e610cb48183611d0f565b9138611380565b60e0366003190112610247576004356115a46024356104af565b6115ad36610ddf565b9060c4356001600160401b038111610247576115cd903690600401610597565b6115d683612b8c565b611748576115e43384612c8b565b6115ed83612b8c565b611748576115fb3384612c8b565b6116086060850135613bd6565b6116156040850135613bf8565b611621610ebb8561457e565b91602085019260016116328561287c565b61163b8161286d565b03611734579061164b9291613f76565b9261165c610672600461066461295b565b6040516369b0777f60e11b81526004810185905290602090829060249082905afa90811561078257600091611715575b508481036116fc57506103df93505b6103da6116a6612f0d565b926116b0846133b2565b6103cc846116bd87613a7e565b926116ca610efb46613a7e565b956116dd6116d733614173565b9261287c565b903591604051978896630bb9f0b760e01b602089015260248801612889565b63023d647f60e41b600052600485905260245260446000fd5b61172e915060203d60201161077b5761076d8183611d0f565b3861168c565b91611743916103df9693614684565b61169b565b8263d08fd27160e01b60005260045260246000fd5b3461024757600036600319011261024757602060405160008051602061558f8339815191528152f35b6001600160a01b0381160361024757565b60e0366003190112610247576004356024356001600160401b03811161024757611896916117cb6020923690600401610597565b6044359060643593611869608435946117e3866104af565b60a4359360c435956117f487611786565b6117fe3386612c8b565b61180a818385886143ff565b61181261295b565b986118586118356106726012611826612f0d565b9d01546001600160a01b031690565b99611850611841611f1c565b6001600160a01b03909b168b52565b1515898d0152565b6040880152606087015236916120c6565b608084015260a083015260c08201526000604051809681958294639414f66b60e01b8452600484016127e4565b03925af1801561078257610a01916000916118bd575b506040519081529081906020820190565b6118d6915060203d60201161077b5761076d8183611d0f565b386118ac565b60c0366003190112610247576004356118f436610df0565b9060a4356001600160401b038111610247576115e4903690600401610597565b346102475760003660031901126102475760206040516000805160206154ef8339815191528152f35b611946366104b9565b50611952839293612b8c565b610470576119603383612c8b565b61196982612b8c565b61047057916119783383612c8b565b61198182612da3565b611989612f0d565b9061199382612fdd565b60405190637992685160e11b60208301528460848301856024850152606060448501525260a4820160a48660051b8401019582600090607e1981360301935b8383106119fa57508589036023190160648701526103df8888886103da816103cc8f85610984565b90919293949860a319878203018252893586811215610247576020611a3260019360806104508885960163ffffffff61041982610a41565b9b019201930191909493926119d2565b602036600319011261024757600435611a5b3382612c8b565b611a6361295b565b90611a886106726012611a74612f0d565b94611a7e86613435565b61066433866146c8565b6040516331a9108f60e11b8152600481018390529290602090849060249082905afa8015610782576103df93600091611af7575b506103da611ad5611acf610efb46613a7e565b92614173565b916103cc84604051948593630225715360e61b602086015288602486016128e8565b611b10915060203d602011611500576114f18183611d0f565b38611abc565b602036600319011261024757600435611b2f3382612c8b565b611b3761295b565b90611b5c6106726004611b48612f0d565b94611b52866134b8565b6106643386614852565b6040516331a9108f60e11b8152600481018390529290602090849060249082905afa8015610782576103df93600091611bc5575b506103da611ba3611acf610efb46613a7e565b916103cc84604051948593633316357f60e21b602086015288602486016128e8565b611bde915060203d602011611500576114f18183611d0f565b38611b90565b346102475760003660031901126102475760206040516000805160206155cf8339815191528152f35b6040366003190112610247576024356004356012611c2961295b565b018054611c3e906001600160a01b0316610672565b60405163cfa651f360e01b81526004810184905290602090829060249082905afa90811561078257600091611cba575b508015610c8a57611c80903390612c8b565b611c8e610672610bad612f0d565b803b1561024757610d7593600080946040519687958694859363fabea1c360e01b855260048501612908565b611cd3915060203d60201161077b5761076d8183611d0f565b38611c6e565b634e487b7160e01b600052604160045260246000fd5b60a081019081106001600160401b03821117611d0a57604052565b611cd9565b90601f801991011681019081106001600160401b03821117611d0a57604052565b519069ffffffffffffffffffff8216820361024757565b908160a091031261024757611d5b81611d30565b916020820151916040810151916104f6608060608401519301611d30565b6040513d6000823e3d90fd5b600460a06001600160a01b036009611d9b61295b565b01541660405192838092633fabe5a360e21b82525afa918215610782576104f69260008093600092611dd9575b509083611dd49261299f565b612ab3565b9050611dd49350611e02915060a03d60a011611e10575b611dfa8183611d0f565b810190611d47565b509194509091905083611dc8565b503d611df0565b60ff81160361024757565b9035601e19823603018112156102475701602081359101916001600160401b03821161024757813603831361024757565b908060209392818452848401376000828201840152601f01601f1916010190565b600a611e7e61295b565b019060005260205260406000205490565b9160206104f6938181520191611e53565b5190611eab826104af565b565b9081602091031261024757516104f6816104af565b90816020910312610247575190565b94906104f6969492611f0e94611ef2611f009360808a5260808a0190610984565b9188830360208a0152611e53565b918583036040870152611e53565b916060818403910152610984565b60405190611eab60e083611d0f565b6001600160401b038111611d0a57601f01601f191660200190565b611eab9850611f553382612c8b565b6123be565b51906001600160401b038216820361024757565b602081830312610247578051906001600160401b03821161024757019060a0828203126102475760405191611fa283611cef565b8051835260208101516001600160401b0381116102475781019180601f8401121561024757825192611fd384611f2b565b91611fe16040519384611d0f565b84835260208583010111610247576080936120029160208085019101610961565b602084015261201360408201611f5a565b604084015261202460608201611ea0565b60608401520151608082015290565b6040906104f6939281528160208201520190610984565b356104f681611786565b903590601e198136030182121561024757018035906001600160401b03821161024757602001918160051b3603831361024757565b634e487b7160e01b600052603260045260246000fd5b91908110156120c15760051b81013590603e1981360301821215610247570190565b612089565b9291926120d282611f2b565b916120e06040519384611d0f565b829481845281830111610247578281602093846000960137010152565b9080601f83011215610247578160206104f6933591016120c6565b6040813603126102475760405190604082018281106001600160401b03821117611d0a5760405280356001600160401b0381116102475761215c90369083016120fd565b82526020810135906001600160401b0382116102475761217e913691016120fd565b602082015290565b9061219960209282815194859201610961565b0190565b90916121b46104f693604084526040840190610984565b916020818403910152610984565b949280979694929160a0870190875260a060208801525260c0850160c08860051b8701019782600090603e1981360301935b838310612237575050505050509061221b8661222993866104f69899036040880152610984565b908482036060860152610984565b916080818403910152610984565b90919293949a60bf198a82030182528b3586811215610247576020612292600193868394019061228561227b61226d8480611e22565b604085526040850191611e53565b9285810190611e22565b9185818503910152611e53565b9d019201930191909493926121f4565b60209291906122b8849282815194859201610961565b019081520190565b6001600160401b038111611d0a5760051b60200190565b604051906122e6602083611d0f565b6000808352366020840137565b908092918237016000815290565b90602083828152019260208260051b82010193836000925b8484106123295750505050505090565b909192939495602080612351600193601f1986820301885261234b8b88611e22565b90611e53565b9801940194019294939190612319565b969261239c906123b0969561238e6104f69b999563ffffffff958c5260c060208d015260c08c0190610984565b918a830360408c0152611e53565b931660608701528583036080870152612301565b9160a0818403910152610984565b94959096939192936123ce61295b565b91841597881561263b575b6123e288612b8c565b976000891561263557506012850154612403906001600160a01b0316610672565b60405163cfa651f360e01b81526004810183905290602090829060249082905afa90811561078257600091612616575b50945b898061260e575b612569575b61244c3383612c8b565b61245582612ed6565b61245d612f0d565b996124678b61353b565b80612560575b1561254057601201546124b39594939291600091612493906001600160a01b0316610672565b60405180809981946316740b0b60e31b8352600483019190602083019252565b03915afa93841561078257611eab9b6103da9860208c976103cc99600091612525575b500151929c15612501575050955b604051631bee0bf560e11b60208201529889978c60248a01612361565b6103cc61251f9293610fc7610fba60405196879560208701916122f3565b956124e4565b61253a91503d806000833e610cb48183611d0f565b386124d6565b506103da9699506103cc94509061251f611eab9b8a9594939b36916120c6565b5085151561246d565b61257c612577368a8f6120c6565b614360565b6012820154612593906001600160a01b0316610672565b60206040518092635da1452f60e01b825281806125b4878a60048401612033565b03915afa908115610782576000916125ef575b506125d25750612442565b60405163c0c9447760e01b81529081906107e790600483016109a9565b612608915060203d6020116107c5576107b78183611d0f565b386125c7565b50851561243d565b61262f915060203d60201161077b5761076d8183611d0f565b38612433565b94612436565b61264e61264936888d6120c6565b6142f3565b6123d9565b90602060ff92600e61266361295b565b01836040519485938437820190815203019020541690565b6000805160206154ef8339815191528114801561277e575b8015612767575b8015612750575b8015612739575b8015612722575b801561270b575b80156126f4575b156126e0576126cb90611e74565b80156126da576104f690611d85565b50600090565b63332ed6cd60e21b60005260045260246000fd5b506000805160206155af83398151915281146126bd565b5060008051602061550f83398151915281146126b6565b5060008051602061558f83398151915281146126af565b5060008051602061552f83398151915281146126a8565b5060008051602061556f83398151915281146126a1565b5060008051602061554f833981519152811461269a565b506000805160206155cf8339815191528114612693565b926127c16104f695936127b3611f0e94608088526080880190610984565b908682036020880152610984565b908482036040860152610984565b9081602091031261024757516104f681611786565b916104f6926040815260018060a01b03835116604082015260208301511515606082015260408301516080820152606083015160a082015260c0612837608085015160e083850152610120840190610984565b9360a081015160e084015201516101008201526020818403910152610984565b634e487b7160e01b600052602160045260246000fd5b6003111561287757565b612857565b3560038110156102475790565b92906128c3926128a76128b59298969860c0875260c0870190610984565b908582036020870152610984565b908382036040850152610984565b926003851015612877576104f6946060830152608082015260a0818403910152610984565b926127c16104f69593611f0e938652608060208701526080860190610984565b6104f69392606092825260208201528160408201520190610984565b634e487b7160e01b600052601160045260246000fd5b60001981019190821161294957565b612924565b9190820391821161294957565b604051602081017f4d4b313ebcb0a912513922edb5e106a88014bff7fd8b7c782480f94eaff75a96815260208252612994604083611d0f565b9051902060ff191690565b9060008113156129e657506201517f1942014281116129495782106129c2575050565b69ffffffffffffffffffff9063142f6edf60e11b6000521660045260245260446000fd5b6338ee04a760e01b60005260045260246000fd5b9081602091031261024757516104f681611e17565b60ff166002039060ff821161294957565b60ff6001199116019060ff821161294957565b60ff16604d811161294957600a0a90565b90670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561294957565b600181901b91906001600160ff1b0381160361294957565b8181029291811591840414171561294957565b8115612a9d570490565b634e487b7160e01b600052601260045260246000fd5b60046020612ac7610672600961066461295b565b60405163313ce56760e01b815292839182905afa90811561078257600091612b5d575b506000831315612b4757600260ff821610612b2b57612b216104f69392612b1b612b16612b2694612a20565b612a33565b90612a80565b612a44565b612a93565b612b416104f693612b1b612b16612b2694612a0f565b91612a44565b6338ee04a760e01b600052600483905260246000fd5b612b7f915060203d602011612b85575b612b778183611d0f565b8101906129fa565b38612aea565b503d612b6d565b612b9461295b565b6004810154612bab906001600160a01b0316610672565b604051634f558e7960e01b81526004810184905290602090829060249082905afa90811561078257600091612c6c575b50612c655760120154612bf6906001600160a01b0316610672565b604051634f558e7960e01b81526004810183905290602090829060249082905afa90811561078257600091612c46575b5015612c325750600190565b63701a312d60e11b60005260045260246000fd5b612c5f915060203d6020116107c5576107b78183611d0f565b38612c26565b5050600090565b612c85915060203d6020116107c5576107b78183611d0f565b38612bdb565b612c9361295b565b612c9c82612b8c565b15612d4d5760120154612cde91602091612cbe906001600160a01b0316610672565b60405180809581946331a9108f60e11b8352600483019190602083019252565b03915afa90811561078257600091612d2e575b50905b6001600160a01b0382811690821603612d0b575050565b63adabb9d960e01b6000526001600160a01b039081166004521660245260446000fd5b612d47915060203d602011611500576114f18183611d0f565b38612cf1565b60040154612d6a91602091612cbe906001600160a01b0316610672565b03915afa90811561078257600091612d84575b5090612cf4565b612d9d915060203d602011611500576114f18183611d0f565b38612d7d565b612dab61295b565b612db482612b8c565b15612e5f576012810154612dd0906001600160a01b0316610672565b6040516369b0777f60e11b81526004810184905290602090829060249082905afa8015610782576001926011612e1d928594600091612e40575b50915b0190600052602052604060002090565b541603612e275750565b631d11134b60e01b600052600452600160245260446000fd5b612e59915060203d60201161077b5761076d8183611d0f565b38612e0a565b6004810154612e76906001600160a01b0316610672565b6040516369b0777f60e11b81526004810184905290602090829060249082905afa8015610782576001926011612e1d928594600091612eb7575b5091612e0d565b612ed0915060203d60201161077b5761076d8183611d0f565b38612eb0565b612ede61295b565b50600280612eeb83614993565b1603612ef45750565b631d11134b60e01b600052600452600260245260446000fd5b600d612f1761295b565b0180546000198114612949576104f6916001820190556040516020810191438352466040830152606082015260608152612f52608082611d0f565b51902060016fffffffffffffffffffffffffffffffff821160071b6001600160401b0383821c1160061b1763ffffffff83821c1160051b1761ffff83821c1160041b1760ff83612fa28360031c90565b921c11170190614c0e565b3d15612fd8573d90612fbe82611f2b565b91612fcc6040519384611d0f565b82523d6000602084013e565b606090565b612fe561295b565b90613016600161301060008051602061552f833981519152600a860190600052602052604060002090565b54612a80565b9182156131f7576009810154613034906001600160a01b0316610672565b604051633fabe5a360e21b815260a081600481855afa8015610782576000906000906000906131c6575b82935061306f90826130759461299f565b86612ab3565b3403613117575b5050600b01546001600160a01b031680156131065760009081908190819034906130ae906001600160a01b0316610672565b5af16130b8612fad565b50156130f5577fe639fa7b09c9280a0841241aaca60ee854005312117168919f20f2cc4b96673c916130f06040519283923484612908565b0390a1565b6312171d8360e31b60005260046000fd5b63d92e233d60e01b60005260046000fd5b61312090614a32565b604051639a6fc8f560e01b815269ffffffffffffffffffff821660048201529160a090839060249082905afa9182156107825761317492600091600091613199575b508161316e929361299f565b84612ab3565b803403613181578061307c565b631e0bd6c160e31b6000526004523460245260446000fd5b61316e92506131b7915060a03d60a011611e1057611dfa8183611d0f565b50919350909150829050613162565b5050506130756131e761306f9260a03d60a011611e1057611dfa8183611d0f565b509294508493509091905061305e565b5050503461320157565b636a71130f60e01b60005260008051602061552f833981519152600452600160245260446000fd5b61323161295b565b9061325c600161301060008051602061556f833981519152600a860190600052602052604060002090565b91821561327a576009810154613034906001600160a01b0316610672565b5050503461328457565b636a71130f60e01b60005260008051602061556f833981519152600452600160245260446000fd5b6132b461295b565b906132df600161301060008051602061554f833981519152600a860190600052602052604060002090565b9182156132fd576009810154613034906001600160a01b0316610672565b5050503461330757565b636a71130f60e01b60005260008051602061554f833981519152600452600160245260446000fd5b9061333861295b565b613361836130106000805160206154ef833981519152600a850190600052602052604060002090565b92831561338057506009810154613034906001600160a01b0316610672565b925050503461338c5750565b636a71130f60e01b6000526000805160206154ef83398151915260045260245260446000fd5b6133ba61295b565b906133e560016130106000805160206155cf833981519152600a860190600052602052604060002090565b918215613403576009810154613034906001600160a01b0316610672565b5050503461340d57565b636a71130f60e01b6000526000805160206155cf833981519152600452600160245260446000fd5b61343d61295b565b9061346860016130106000805160206155af833981519152600a860190600052602052604060002090565b918215613486576009810154613034906001600160a01b0316610672565b5050503461349057565b636a71130f60e01b6000526000805160206155af833981519152600452600160245260446000fd5b6134c061295b565b906134eb600161301060008051602061550f833981519152600a860190600052602052604060002090565b918215613509576009810154613034906001600160a01b0316610672565b5050503461351357565b636a71130f60e01b60005260008051602061550f833981519152600452600160245260446000fd5b61354361295b565b9061356e600161301060008051602061558f833981519152600a860190600052602052604060002090565b91821561358c576009810154613034906001600160a01b0316610672565b5050503461359657565b636a71130f60e01b60005260008051602061558f833981519152600452600160245260446000fd5b604051606091906135cf8382611d0f565b6002815291601f19018260005b8281106135e857505050565b8060606020809385010152016135dc565b6040805190919061360a8382611d0f565b6001815291601f19018260005b82811061362357505050565b806060602080938501015201613617565b8051600110156120c15760400190565b8051156120c15760200190565b80518210156120c15760209160051b010190565b90600182811c92168015613695575b602083101461367f57565b634e487b7160e01b600052602260045260246000fd5b91607f1691613674565b90604051918260008254926136b384613665565b808452936001811690811561371f57506001146136d8575b50611eab92500383611d0f565b90506000929192526020600020906000915b818310613703575050906020611eab92820101386136cb565b60209193508060019154838589010152019101909184926136ea565b905060209250611eab94915060ff191682840152151560051b820101386136cb565b9080602083519182815201916020808360051b8301019401926000915b83831061376d57505050505090565b909192939460208061378b600193601f198682030187528951610984565b9701930193019193929061375e565b92949391608084526000918054906137b182613665565b9182608088015260018116908160001461383757506001146137f8575b5050946127c1826104f69697866137ea95036020880152610984565b916060818403910152613741565b600090815260208120929350915b818310613820575050830160a001906127c16104f66137ce565b805460a08488010152602090920191600101613806565b60ff191660a08881019190915292151560051b870190920193506127c191506104f690506137ce565b81519192916139455760209192506103cc61389f61387c6135f9565b945b604051633b325fc560e01b8682015260248101919091529182906044820190565b6138a884613644565b526138b283613644565b506138bb61295b565b60038101546138d2906001600160a01b0316610672565b9060006138f860058301926138f260066138eb8661369f565b920161369f565b90614a73565b9161391960405197889687958694631f3f66f760e21b86526004860161379a565b03925af180156107825761392a5750565b6139429060203d60201161077b5761076d8183611d0f565b50565b6103cc61389f6020936103cc61397861395c6135be565b9760405192839163f23f601b60e01b8a840152602483016109a9565b61398187613634565b5261398b86613634565b5061387e565b90916121b46104f69360408452604084019061108e565b6139b061295b565b6139ba60016122c0565b916139c86040519384611d0f565b600183526139d660016122c0565b6020840190601f19013682378351156120c1575260040154613a00906001600160a01b0316610672565b91823b1561024757613a2c926000928360405180968195829463a4362b3960e01b845260048401613991565b03925af1801561078257613a3d5750565b80610c416000611eab93611d0f565b90613a5682611f2b565b613a636040519182611d0f565b8281528092613a74601f1991611f2b565b0190602036910137565b8060009172184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b821015613bb3575b806d04ee2d6d415b85acef8100000000600a921015613b97575b662386f26fc10000811015613b82575b6305f5e100811015613b70575b612710811015613b60575b6064811015613b51575b1015613b46575b613b316021613b0560018501613a4c565b938401015b60001901916f181899199a1a9b1b9c1cb0b131b232b360811b600a82061a8353600a900490565b8015613b4157613b319091613b0a565b505090565b600190910190613af4565b60029060649004930192613aed565b6004906127109004930192613ae3565b6008906305f5e1009004930192613ad8565b601090662386f26fc100009004930192613acb565b6020906d04ee2d6d415b85acef81000000009004930192613abb565b506040915072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8104613aa1565b428110613be05750565b633bd4ab4160e01b6000526004524260245260446000fd5b6002613c0261295b565b01908060005281602052600160ff60406000205416151514613c36576000526020526040600020600160ff19825416179055565b632472ad4160e21b60005260045260246000fd5b90613c54826122c0565b613c616040519182611d0f565b8281528092613a74601f19916122c0565b60405190613c81604083611d0f565b601f82527f4e616d65496e666f28737472696e6720736c642c737472696e6720746c6429006020830152565b613cb5613c72565b604051613ce360208281613cd28183019687815193849201610961565b81010301601f198101835282611d0f565b51902090565b604051613cf7608082611d0f565b605a815260208101907f546f6b656e697a6174696f6e566f7563686572284e616d65496e666f5b5d206e82527f616d65732c75696e74323536206e6f6e63652c75696e7432353620657870697260408201527f657341742c61646472657373206f776e657241646472657373290000000000006060820152613ce3613d7a613c72565b916020604051938492613d9583850197889251928391610961565b8301613da982518093858085019101610961565b010103601f198101835282611d0f565b805160209091019060005b818110613dd15750505090565b8251845260209384019390920191600101613dc4565b613dfb613df48280612054565b9050613c4a565b9060005b613e098280612054565b9050811015613e7c5780613e26610f75600193610f6f8680612054565b613e2e613cad565b815180516020918201209281015180519082012060408051928301938452820193909352606081019290925290613e6881608081016103cc565b519020613e758286613651565b5201613dff565b50613ce3613e88613ce9565b92604051613e9e816103cc602082018095613db9565b5190206103cc602084013593613ebb60606040830135920161204a565b604080516020810198895290810194909452606084019590955260808301526001600160a01b0390931660a082015291829060c0820190565b604290613eff61535a565b613f076153c4565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152613f5860c082611d0f565b519020906040519161190160f01b8352600283015260228201522090565b613f88613f8e92613f979436916120c6565b90614caf565b90929192614cf5565b613fba81613fa361295b565b9060018060a01b0316600052602052604060002090565b54908115613fc6575090565b63bf18af4360e01b60009081526001600160a01b0391909116600452602490fd5b613ff081614d77565b15613ff85750565b604051630a9644cf60e01b8152602060048201529081906107e7906024830190610984565b61402681615197565b1561402e5750565b604051637f19f48d60e01b8152602060048201529081906107e7906024830190610984565b8051600110156120c15760210190565b8051600210156120c15760220190565b8051600310156120c15760230190565b9081518110156120c1570160200190565b8015612949576000190190565b6000815191825b6140b3575b50905090565b6000198301838111612949575b80151580614115575b156140dc576140d790614094565b6140c0565b916140f56140ee846140fb949661294e565b8486614b42565b90614bce565b9080156000036140ad5761410e9061293a565b91826140a8565b50601760f91b6001600160f81b03196141476141396141338561293a565b86614083565b516001600160f81b03191690565b1614156140c9565b6040519061415e604083611d0f565b600682526565697031353560d01b6020830152565b6001600160a01b03168061419761419261418d6014612a68565b614337565b613a4c565b9160306141a384613644565b5360786141af84614053565b536141c26141bd6014612a68565b614345565b60018111614277575061425e57506028602282012060601c6029905b600182116141eb57505090565b614209816007600f61420f9416118061423c575b6142155760041c90565b91614094565b906141de565b600160fd1b6142276141398688614083565b1860001a6142358587614083565b5360041c90565b50606060ff614257614251614139888a614083565b60f81c90565b16116141ff565b63e22e27eb60e01b600052600452601460245260446000fd5b90600f811660108110156120c1576142ad91614209916f181899199a1a9b1b9c1cb0b131b232b360811b901a6142358588614083565b6141c2565b604051906142c1604083611d0f565b6004825263444f4d4160e01b6020830152565b604051906142e3604083611d0f565b60018252603160f81b6020830152565b6142fc81615285565b156143045750565b60405163d4c19e3b60e01b8152602060048201529081906107e7906024830190610984565b90601f820180921161294957565b906002820180921161294957565b906001820180921161294957565b9190820180921161294957565b805180156143fb57805b614372575090565b601760f91b6143966143896141396141338561293a565b6001600160f81b03191690565b146143aa576143a490614094565b8061436a565b6143ba614192828495945161294e565b9060005b82518110156143f457806143e06141396143da60019486614353565b88614083565b60001a6143ed8286614083565b53016143be565b5090925050565b5090565b9161440861295b565b61441461063a85612b8c565b614568576012015461442e906001600160a01b0316610672565b6040516364f1992360e01b815260048101859052602081602481855afa90811561078257600091614549575b5061453357604051633329dc9d60e11b81526004810185905290602090829060249082905afa90811561078257600091614514575b504281106144fb57506144ac916144a79136916120c6565b613fe7565b6002808316036144e25781806144c183614993565b16036144cb575050565b631d11134b60e01b60005260045260245260446000fd5b631d11134b60e01b600052600452600260245260446000fd5b636581bb3f60e11b600052600484905260245260446000fd5b61452d915060203d60201161077b5761076d8183611d0f565b3861448f565b63a9de8c6f60e01b600052600484905260246000fd5b614562915060203d6020116107c5576107b78183611d0f565b3861445a565b635a55e97160e11b600052600484905260246000fd5b61458a6020820161287c565b600381101561287757613ce36040516145a460a082611d0f565b6062815261742960f01b608060208301927f50726f6f664f66436f6e7461637473566f75636865722875696e74323536207284527f656769737472616e7448616e646c652c75696e74382070726f6f66536f75726360408201527f652c75696e74323536206e6f6e63652c75696e743235362065787069726573416060820152015260405161463760208201809361093b565b60628152614646608282611d0f565b519020604080516020810192835285358183015260ff9094166060858101919091529085013560808501529093013560a08301528160c081016103cc565b613f88613f8e926146969436916120c6565b60ff6146be8260016146a661295b565b019060018060a01b0316600052602052604060002090565b541615613fc65750565b906146d161295b565b906146de61063a84612b8c565b61483c5760126146f5926106646106729386612c8b565b6040516364f1992360e01b815260048101839052909190602081602481865afa9081156107825760009161481d575b5061480957604051633329dc9d60e11b815260048101829052602081602481865afa908115610782576000916147ea575b504281106147d0575060405163a34e303d60e01b81526004810182905291602090839060249082905afa918215610782576000926147af575b5081614798575050565b63f9f7458560e01b60005260045260245260446000fd5b6147c991925060203d60201161077b5761076d8183611d0f565b903861478e565b636581bb3f60e11b60005260049190915260245260446000fd5b614803915060203d60201161077b5761076d8183611d0f565b38614755565b63a9de8c6f60e01b60005260045260246000fd5b614836915060203d6020116107c5576107b78183611d0f565b38614724565b635a55e97160e11b600052600483905260246000fd5b9061485b61295b565b9061486583612b8c565b61497d576148739083612c8b565b60028061487f84614993565b1603614962576004015461489b906001600160a01b0316610672565b6040516364f1992360e01b815260048101839052909190602081602481865afa90811561078257600091614943575b5061480957604051633329dc9d60e11b81526004810182905291602090839060249082905afa91821561078257600092614922575b5042821061490b575050565b636581bb3f60e11b60005260045260245260446000fd5b61493c91925060203d60201161077b5761076d8183611d0f565b90386148ff565b61495c915060203d6020116107c5576107b78183611d0f565b386148ca565b631d11134b60e01b6000526004829052600260245260446000fd5b630bfffc8160e31b600052600483905260246000fd5b61499b61295b565b6149a482612b8c565b15614a1557601201546149e6916020916149c6906001600160a01b0316610672565b60405180809581946332b9c2b360e21b8352600483019190602083019252565b03915afa908115610782576000916149fc575090565b6104f6915060203d60201161077b5761076d8183611d0f565b600401546149e6916020916149c6906001600160a01b0316610672565b6001600160401b03811660018111156143fb57600019016001600160401b038111612949576001600160401b031669ffff0000000000000000919091161790565b6001611eab9193929360206040519582614a968894518092858088019101610961565b8301601d60f91b83820152614ab48251809385602185019101610961565b01010301601f198101845283611d0f565b15614acc57565b60405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b6044820152606490fd5b15614b0957565b60405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b6044820152606490fd5b91614b5781614b5081614329565b1015614ac5565b614b6d8351614b668385614353565b1115614b02565b80614b8657505050604051600081526020810160405290565b60405192601f821692831560051b80858701019484860193010101905b808410614bbb5750508252601f01601f191660405290565b9092602080918551815201930190614ba3565b90604051614bec60208281613cd28183019687815193849201610961565b5190206040519060208201928352604082015260408152613ce3606082611d0f565b9081614c1f61419261418d84612a68565b926030614c2b85613644565b536078614c3785614053565b53614c446141bd84612a68565b60018111614c6d5750614c5657505090565b63e22e27eb60e01b60005260045260245260446000fd5b90600f81169060108210156120c157614caa916f181899199a1a9b1b9c1cb0b131b232b360811b901a614ca08488614083565b5360041c91614094565b614c44565b8151919060418303614ce057614cd992506020820151906060604084015193015160001a90615409565b9192909190565b505060009160029190565b6004111561287757565b614cfe81614ceb565b80614d07575050565b614d1081614ceb565b60018103614d295763f645eedf60e01b60005260046000fd5b614d3281614ceb565b60028103614d4f575063fce698f760e01b60005260045260246000fd5b80614d5b600392614ceb565b14614d635750565b6335e2f38360e21b60005260045260246000fd5b805180158015614f71575b612c6557600481119081614f4d575b81614f29575b81614f05575b81614ee1575b60005b818110614db65750505050600190565b614dc66143896141398387614083565b606160f81b81101580614ed3575b8015614eb6575b8015614ea9575b8015614ea1575b15614e795781158015614e90575b80614e83575b614e7957600282149081614e6b575b5080614e5a575b80614e35575b614e26575b600101614da6565b82614e1e575b50505050600090565b50602d60f81b6001600160f81b0319614e536141396143da85614345565b1614614e19565b5081614e6582614345565b10614e13565b602d60f81b14905038614e0c565b5050505050600090565b50602d60f81b8114614dfd565b50614e9a8361293a565b8214614df7565b506000614de9565b50602d60f81b8114614de2565b50600360fc1b8110801590614ddb5750603960f81b811115614ddb565b50603d60f91b811115614dd4565b9050602d60f81b6001600160f81b0319614efd61413985614073565b161490614da3565b9050602d60f81b6001600160f81b0319614f2161413985614063565b161490614d9d565b9050603760f91b6001600160f81b0319614f4561413985614053565b161490614d97565b9050600f60fb1b6001600160f81b0319614f6961413985613644565b161490614d91565b50603f8111614d82565b8051918215801561518d575b61518557600483119182615161575b8261513d575b82615119575b826150f5575b60005b848110614fbc575050505050600190565b614fcc6143896141398385614083565b606160f81b811015806150e7575b80156150ca575b80156150bd575b80156150a8575b1561507f5781158015615097575b8061508a575b61507f57600282149081615071575b5080615060575b8061503b575b61502c575b600101614fab565b83615024575050505050600090565b50602d60f81b6001600160f81b031961505961413961413385614345565b161461501f565b508461506b82614345565b10615019565b602d60f81b14905038615012565b505050505050600090565b50602d60f81b8114615003565b506150a18661293a565b8214614ffd565b50838015614fef5750605f60f81b8114614fef565b50602d60f81b8114614fe8565b50600360fc1b8110801590614fe15750603960f81b811115614fe1565b50603d60f91b811115614fda565b9150602d60f81b6001600160f81b031961511161413985614073565b161491614fa8565b9150602d60f81b6001600160f81b031961513561413985614063565b161491614fa2565b9150603760f91b6001600160f81b031961515961413985614053565b161491614f9c565b9150600f60fb1b6001600160f81b031961517d61413985613644565b161491614f96565b505050600090565b50603f8311614f87565b8051908115801561527b575b612c6557601760f91b6001600160f81b03196151c161413984613644565b16148015615257575b612c655760009060005b83811061520257506151ef6151f49361063a93600093615492565b614f7b565b6151fd57600190565b600090565b601760f91b6152176143896141398486614083565b14615225575b6001016151d4565b91808314614e2c5761063a60006151ef856152409486615492565b61518557600161524f83614345565b92905061521d565b50601760f91b61527561438961413961526f8661293a565b85614083565b146151ca565b5060fd82116151a3565b80519081158015615350575b612c6557601760f91b6001600160f81b03196152af61413984613644565b16148015615332575b612c655760009060005b8381106152dd57506151ef6151f49361063a93600193615492565b601760f91b6152f26143896141398486614083565b14615300575b6001016152c2565b91808314614e2c5761063a60016151ef8561531b9486615492565b61518557600161532a83614345565b9290506152f8565b50601760f91b61534a61438961413961526f8661293a565b146152b8565b5060fd8211615291565b6153626142b2565b8051908115615372576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054801561539f5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b6153cc6142d4565b80519081156153dc576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10154801561539f5790565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615486579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa15610782576000516001600160a01b0381161561547a5790600090600090565b50600090600190600090565b50505060009160039190565b91818103818111612949576154a690613a4c565b92825b8281106154b7575050505090565b6001600160f81b03196154ca8284614083565b511690848103818111612949576154e760019360001a9188614083565b53016154a956fe41274d8380e68ced671e39b24773a9a28190d6ae058a9a0a57272be30902055c4a2248149817dde7dfda12ec5979900c9a0aba1d5ecdd54d38762a0cc40039961ee9a1c13af222b9e4d98567d0238a554fbf843b89f4720f3aa34731720cdffa08fb31c3e81624356c3314088aa971b73bcc82d22bc3e3b184b4593077ae3278add9df460364fdc361c17268aded1a218d04bba51bfce5119914d05b8bd10ababbecd033b99e35d8972b316640f0b13a68063e02065b4e964541d3498c941657272cf3b1b4a64c268809c75d1290f1c3ac8c1e2df8ebefee53369069b86f0649a19646f5f88bada8fee59cab6efcdf841c1797d1344642f9af3a4e0415431506a26469706673582212205d03e947712de15fd7c777d5d9ce651c353462302ef3f97fcafe502bcfa078fc64736f6c634300081c0033

Deployed Bytecode

0x6080604052600436101561001257600080fd5b60003560e01c80630cbc4e93146102375780630e160be6146102325780632f945faa1461022d57806335abf04a146102285780633761014014610223578063379006ba1461021e5780633827aaaf1461021957806340fb6b8e1461021457806354fd4d501461020f578063575c472d1461020a578063628c19061461020557806373d9d085146102005780637f13e412146101fb5780638141f9ed146101f657806384b0196e146101f157806386d45941146101ec578063870a3d84146101e7578063989082f9146101e25780639c7d2ab7146101dd578063a28df472146101d8578063aa00c548146101d3578063ade81788146101ce578063b36210c1146101c9578063ba26a79d146101c4578063bce69e8c146101bf578063c4851c0c146101ba578063de174837146101b5578063e89174bd146101b0578063e952a25a146101ab578063ea0b1829146101a6578063eabd785c146101a1578063eb7ce2491461019c578063efd0c5a6146101975763efea996a1461019257600080fd5b611c0d565b611be4565b611b16565b611a42565b61193d565b611914565b6118dc565b611797565b61175d565b61158a565b61131d565b611304565b6112f0565b6112c7565b6112af565b61126a565b6111e8565b61116b565b611142565b6110c2565b610e0f565b610cc3565b610ae6565b610a52565b610a05565b6109ba565b610800565b6105c4565b6104f9565b610497565b610327565b61029b565b610275565b61024c565b600091031261024757565b600080fd5b3461024757600036600319011261024757602060405160008051602061550f8339815191528152f35b34610247576020366003190112610247576020610293600435611d85565b604051908152f35b3461024757600036600319011261024757602060405160008051602061556f8339815191528152f35b9181601f84011215610247578235916001600160401b038311610247576020808501948460051b01011161024757565b9060406003198301126102475760043591602435906001600160401b03821161024757610323916004016102c4565b9091565b610330366102f4565b61033b839293612b8c565b610470579161034a3383612c8b565b61035382612da3565b61035b612f0d565b9061036582612fdd565b60405190637992685160e11b60208301528460848301856024850152606060448501525260a4820160a48660051b8401019582600090607e1981360301935b8383106103e157508589036023190160648701526103df8888886103da816103cc8f85610984565b03601f198101835282611d0f565b613860565b005b90919293949860a31987820301825289358681121561024757602061046060019360806104508885960163ffffffff61041982610a41565b16845260ff8682013561042b81611e17565b168685015260ff604082013561044081611e17565b1660408501526060810190611e22565b9190928160608201520191611e53565b9b019201930191909493926103a4565b5063d08fd27160e01b60005260045260246000fd5b60209060031901126102475760043590565b346102475760206102936104aa36610485565b611e74565b8015150361024757565b9060606003198301126102475760043591602435906001600160401b038211610247576104e8916004016102c4565b90916044356104f6816104af565b90565b610502366104b9565b5061050e839293612b8c565b6104705761051c3383612c8b565b61052582612b8c565b610470576103df926105373384612c8b565b61054083612da3565b6103da61054b612f0d565b9261055584613229565b6103cc610585604051948593639f63ae2360e01b6020860152886024860152606060448601526084850191612301565b82810360231901606484015285610984565b9181601f84011215610247578235916001600160401b038311610247576020838186019501011161024757565b6060366003190112610247576004356024356001600160401b038111610247576105f2903690600401610597565b91906044356001600160401b03811161024757610613903690600401610597565b61061f84939293612b8c565b6107eb5761062d3385612c8b565b61063e61063a8684612653565b1590565b6107cc5761064a612f0d565b92610654846132ac565b61067e610672600461066461295b565b01546001600160a01b031690565b6001600160a01b031690565b6040516364f1992360e01b81526004810187905290602090829060249082905afa9081156107825760009161079d575b50610787576106c3610672600461066461295b565b604051633329dc9d60e11b81526004810187905290602090829060249082905afa90811561078257600091610753575b504281106107395750946103cc846103da936103df98610713838a6139a8565b61071c89613a7e565b60405163069322e360e51b60208201529788969160248801611ed1565b636581bb3f60e11b600052600486905260245260445b6000fd5b610775915060203d60201161077b575b61076d8183611d0f565b810190611ec2565b386106f3565b503d610763565b611d79565b63a9de8c6f60e01b600052600485905260246000fd5b6107bf915060203d6020116107c5575b6107b78183611d0f565b810190611ead565b386106ae565b503d6107ad565b604051620d153960e01b8152806107e7878560048401611e8f565b0390fd5b8363d08fd27160e01b60005260045260246000fd5b346102475760403660031901126102475760043561081f6024356104af565b61082881612b8c565b610901576108363382612c8b565b61083f81612b8c565b610901576103df906108513382612c8b565b610859612f0d565b6108ae6103da61086884613a7e565b6103cc8461087546613a7e565b6108dd600160409260266020855161088d8782611d0f565b6006815201916565697031353560d01b835285519a8b936020850190610915565b601d60f91b828401526108cb815180926020602787019101610961565b8201010301601f198101885287611d0f565b6108e633614173565b9051631acdf90760e21b602082015295869460248601612795565b63d08fd27160e01b60005260045260246000fd5b60005b6006811061092b57505060066000910152565b8181015183820152602001610918565b60005b6062811061095157505060626000910152565b818101518382015260200161093e565b60005b8381106109745750506000910152565b8181015183820152602001610964565b9060209161099d81518092818552858086019101610961565b601f01601f1916010190565b9060206104f6928181520190610984565b3461024757600036600319011261024757610a0160408051906109dd8183611d0f565b60058252640312e312e360dc1b602083015251918291602083526020830190610984565b0390f35b3461024757600036600319011261024757602060405160008051602061552f8339815191528152f35b6064359063ffffffff8216820361024757565b359063ffffffff8216820361024757565b60c0366003190112610247576004356024356001600160401b03811161024757610a80903690600401610597565b6044929192356001600160401b03811161024757610aa2903690600401610597565b610aad949194610a2e565b90608435956001600160401b03871161024757610ad16103df9736906004016102c4565b95909460a43597610ae1896104af565b611f46565b3461024757602036600319011261024757600435610b043382612c8b565b6012610b0e61295b565b018054909190610b26906001600160a01b0316610672565b6040516316740b0b60e31b8152600481018390529092600082602481875afa91821561078257600092610ca0575b50815115610c8a578151608083018051604051633d854fa160e11b81526004810193909352602483015294602090829060449082905afa90811561078257600091610c6b575b50610c4d57610bbb610672610bad612f0d565b92546001600160a01b031690565b803b156102475760405163576bd72160e01b8152916000918391829084908290610be9908a60048401612033565b03925af1801561078257610c32575b505191516040519081523391907f5489bf68a3ad4b454ddf44e2cc0863dd86a8a874fcf2191c8818b6908bd1a4eb9080602081015b0390a4005b80610c416000610c4793611d0f565b8061023c565b38610bf8565b815184516314f241a760e01b60005260049190915260245260446000fd5b610c84915060203d6020116107c5576107b78183611d0f565b38610b9a565b632db04fed60e21b600052600483905260246000fd5b610cbc91923d8091833e610cb48183611d0f565b810190611f6e565b9038610b54565b3461024757604036600319011261024757602435600435610ce43382612c8b565b610cec61295b565b610cf861063a83612b8c565b610dc95760120154610d12906001600160a01b0316610672565b60405163cfa651f360e01b815260048101839052909290602081602481875afa90811561078257600091610daa575b50610d9457823b1561024757604051633f89f20960e11b815260048101929092526024820152906000908290818381604481015b03925af1801561078257610d8557005b80610c4160006103df93611d0f565b63ed15e6cf60e01b600052600482905260246000fd5b610dc3915060203d60201161077b5761076d8183611d0f565b38610d41565b635a55e97160e11b600052600482905260246000fd5b608090604319011261024757604490565b608090602319011261024757602490565b908160809103126102475790565b6040366003190112610247576004356001600160401b03811161024757610e3a903690600401610e01565b6024356001600160401b03811161024757610e59903690600401610597565b9190610e686040830135613bd6565b610e756020830135613bf8565b6060820191610e866106728461204a565b330361106257610ec5610e998280612054565b959050610ea4612f0d565b93610eaf858861332f565b610ec0610ebb85613de7565b613ef4565b613f76565b9360005b818110610f5e5750506103df93610f3083610ee784610f3e95612054565b9096610f13610f0e610f08610efb46613a7e565b610f0361414f565b614a73565b9261204a565b614173565b9160405198899663b5999e7960e01b6020890152602488016121c2565b03601f198101845283611d0f565b6040516020810190610f55816103cc4387866122a2565b51902091613860565b610f7a610f7582610f6f8680612054565b9061209f565b612118565b610f848151613fe7565b6020810190610f93825161401d565b6110026020610fd2610fba610fcd85516103cc8851610fc760405195869489860190612186565b601760f91b815260010190565b90612186565b6140a1565b610fe2610672600461066461295b565b6040518080958194634f558e7960e01b8352600483019190602083019252565b03915afa90811561078257600091611044575b50611024575050600101610ec9565b519051604051630e3ba82b60e31b81529182916107e7916004840161219d565b61105c915060203d81116107c5576107b78183611d0f565b38611015565b61074f61106e8461204a565b63adabb9d960e01b600052336004526001600160a01b0316602452604490565b906020808351928381520192019060005b8181106110ac5750505090565b825184526020938401939092019160010161109f565b34610247576000366003190112610247576111136110de6142b2565b610a016110e96142d4565b6111216110f46122d7565b91604051958695600f60f81b875260e0602088015260e0870190610984565b908582036040870152610984565b90466060850152306080850152600060a085015283820360c085015261108e565b3461024757600036600319011261024757602060405160008051602061554f8339815191528152f35b6080366003190112610247576004356111856024356104af565b6044356001600160401b038111610247576111a4903690600401610597565b91906064356001600160401b038111610247576111c5903690600401610597565b6111d184939293612b8c565b6107eb576111df3385612c8b565b61061f84612b8c565b60a0366003190112610247576004356024356001600160401b03811161024757611216903690600401610597565b916044356001600160401b03811161024757611236903690600401610597565b61123e610a2e565b91608435956001600160401b038711610247576112626103df9736906004016102c4565b9690956123be565b34610247576020366003190112610247576004356001600160401b038111610247576112a561129f6020923690600401610597565b90612653565b6040519015158152f35b346102475760206102936112c236610485565b61267b565b346102475760003660031901126102475760206040516000805160206155af8339815191528152f35b6112f9366102f4565b610525839293612b8c565b3461024757602036600319011261024757600435610836565b3461024757602036600319011261024757600435601261133b61295b565b018054611350906001600160a01b0316610672565b6040516316740b0b60e31b81526004810184905290929091600083602481875afa9283156107825760009361156f575b508251156115595761139483513390612c8b565b6113a461063a6060850151151590565b611543578251608084018051604051633d854fa160e11b8152600481019390935260248301529390602081604481895afa90811561078257600091611524575b50611507576040516331a9108f60e11b81526004810184905294602090869060249082905afa948515610782576000956114d6575b50611436610672611428612f0d565b93546001600160a01b031690565b803b156102475760405163576bd72160e01b8152926000918491829084908290611464908a60048401612033565b03925af1908115610782577f676dac521fa16607d417251e9ab50656928e72849236f2c82525fdbfa23d2cd492610c2d926114c1575b5051935160408051958652602086019190915233956001600160a01b031694918291820190565b80610c4160006114d093611d0f565b3861149a565b6114f991955060203d602011611500575b6114f18183611d0f565b8101906127cf565b9338611419565b503d6114e7565b5183516314f241a760e01b60005260049190915260245260446000fd5b61153d915060203d6020116107c5576107b78183611d0f565b386113e4565b63117c7f6560e01b600052600482905260246000fd5b632db04fed60e21b600052600482905260246000fd5b61158391933d8091833e610cb48183611d0f565b9138611380565b60e0366003190112610247576004356115a46024356104af565b6115ad36610ddf565b9060c4356001600160401b038111610247576115cd903690600401610597565b6115d683612b8c565b611748576115e43384612c8b565b6115ed83612b8c565b611748576115fb3384612c8b565b6116086060850135613bd6565b6116156040850135613bf8565b611621610ebb8561457e565b91602085019260016116328561287c565b61163b8161286d565b03611734579061164b9291613f76565b9261165c610672600461066461295b565b6040516369b0777f60e11b81526004810185905290602090829060249082905afa90811561078257600091611715575b508481036116fc57506103df93505b6103da6116a6612f0d565b926116b0846133b2565b6103cc846116bd87613a7e565b926116ca610efb46613a7e565b956116dd6116d733614173565b9261287c565b903591604051978896630bb9f0b760e01b602089015260248801612889565b63023d647f60e41b600052600485905260245260446000fd5b61172e915060203d60201161077b5761076d8183611d0f565b3861168c565b91611743916103df9693614684565b61169b565b8263d08fd27160e01b60005260045260246000fd5b3461024757600036600319011261024757602060405160008051602061558f8339815191528152f35b6001600160a01b0381160361024757565b60e0366003190112610247576004356024356001600160401b03811161024757611896916117cb6020923690600401610597565b6044359060643593611869608435946117e3866104af565b60a4359360c435956117f487611786565b6117fe3386612c8b565b61180a818385886143ff565b61181261295b565b986118586118356106726012611826612f0d565b9d01546001600160a01b031690565b99611850611841611f1c565b6001600160a01b03909b168b52565b1515898d0152565b6040880152606087015236916120c6565b608084015260a083015260c08201526000604051809681958294639414f66b60e01b8452600484016127e4565b03925af1801561078257610a01916000916118bd575b506040519081529081906020820190565b6118d6915060203d60201161077b5761076d8183611d0f565b386118ac565b60c0366003190112610247576004356118f436610df0565b9060a4356001600160401b038111610247576115e4903690600401610597565b346102475760003660031901126102475760206040516000805160206154ef8339815191528152f35b611946366104b9565b50611952839293612b8c565b610470576119603383612c8b565b61196982612b8c565b61047057916119783383612c8b565b61198182612da3565b611989612f0d565b9061199382612fdd565b60405190637992685160e11b60208301528460848301856024850152606060448501525260a4820160a48660051b8401019582600090607e1981360301935b8383106119fa57508589036023190160648701526103df8888886103da816103cc8f85610984565b90919293949860a319878203018252893586811215610247576020611a3260019360806104508885960163ffffffff61041982610a41565b9b019201930191909493926119d2565b602036600319011261024757600435611a5b3382612c8b565b611a6361295b565b90611a886106726012611a74612f0d565b94611a7e86613435565b61066433866146c8565b6040516331a9108f60e11b8152600481018390529290602090849060249082905afa8015610782576103df93600091611af7575b506103da611ad5611acf610efb46613a7e565b92614173565b916103cc84604051948593630225715360e61b602086015288602486016128e8565b611b10915060203d602011611500576114f18183611d0f565b38611abc565b602036600319011261024757600435611b2f3382612c8b565b611b3761295b565b90611b5c6106726004611b48612f0d565b94611b52866134b8565b6106643386614852565b6040516331a9108f60e11b8152600481018390529290602090849060249082905afa8015610782576103df93600091611bc5575b506103da611ba3611acf610efb46613a7e565b916103cc84604051948593633316357f60e21b602086015288602486016128e8565b611bde915060203d602011611500576114f18183611d0f565b38611b90565b346102475760003660031901126102475760206040516000805160206155cf8339815191528152f35b6040366003190112610247576024356004356012611c2961295b565b018054611c3e906001600160a01b0316610672565b60405163cfa651f360e01b81526004810184905290602090829060249082905afa90811561078257600091611cba575b508015610c8a57611c80903390612c8b565b611c8e610672610bad612f0d565b803b1561024757610d7593600080946040519687958694859363fabea1c360e01b855260048501612908565b611cd3915060203d60201161077b5761076d8183611d0f565b38611c6e565b634e487b7160e01b600052604160045260246000fd5b60a081019081106001600160401b03821117611d0a57604052565b611cd9565b90601f801991011681019081106001600160401b03821117611d0a57604052565b519069ffffffffffffffffffff8216820361024757565b908160a091031261024757611d5b81611d30565b916020820151916040810151916104f6608060608401519301611d30565b6040513d6000823e3d90fd5b600460a06001600160a01b036009611d9b61295b565b01541660405192838092633fabe5a360e21b82525afa918215610782576104f69260008093600092611dd9575b509083611dd49261299f565b612ab3565b9050611dd49350611e02915060a03d60a011611e10575b611dfa8183611d0f565b810190611d47565b509194509091905083611dc8565b503d611df0565b60ff81160361024757565b9035601e19823603018112156102475701602081359101916001600160401b03821161024757813603831361024757565b908060209392818452848401376000828201840152601f01601f1916010190565b600a611e7e61295b565b019060005260205260406000205490565b9160206104f6938181520191611e53565b5190611eab826104af565b565b9081602091031261024757516104f6816104af565b90816020910312610247575190565b94906104f6969492611f0e94611ef2611f009360808a5260808a0190610984565b9188830360208a0152611e53565b918583036040870152611e53565b916060818403910152610984565b60405190611eab60e083611d0f565b6001600160401b038111611d0a57601f01601f191660200190565b611eab9850611f553382612c8b565b6123be565b51906001600160401b038216820361024757565b602081830312610247578051906001600160401b03821161024757019060a0828203126102475760405191611fa283611cef565b8051835260208101516001600160401b0381116102475781019180601f8401121561024757825192611fd384611f2b565b91611fe16040519384611d0f565b84835260208583010111610247576080936120029160208085019101610961565b602084015261201360408201611f5a565b604084015261202460608201611ea0565b60608401520151608082015290565b6040906104f6939281528160208201520190610984565b356104f681611786565b903590601e198136030182121561024757018035906001600160401b03821161024757602001918160051b3603831361024757565b634e487b7160e01b600052603260045260246000fd5b91908110156120c15760051b81013590603e1981360301821215610247570190565b612089565b9291926120d282611f2b565b916120e06040519384611d0f565b829481845281830111610247578281602093846000960137010152565b9080601f83011215610247578160206104f6933591016120c6565b6040813603126102475760405190604082018281106001600160401b03821117611d0a5760405280356001600160401b0381116102475761215c90369083016120fd565b82526020810135906001600160401b0382116102475761217e913691016120fd565b602082015290565b9061219960209282815194859201610961565b0190565b90916121b46104f693604084526040840190610984565b916020818403910152610984565b949280979694929160a0870190875260a060208801525260c0850160c08860051b8701019782600090603e1981360301935b838310612237575050505050509061221b8661222993866104f69899036040880152610984565b908482036060860152610984565b916080818403910152610984565b90919293949a60bf198a82030182528b3586811215610247576020612292600193868394019061228561227b61226d8480611e22565b604085526040850191611e53565b9285810190611e22565b9185818503910152611e53565b9d019201930191909493926121f4565b60209291906122b8849282815194859201610961565b019081520190565b6001600160401b038111611d0a5760051b60200190565b604051906122e6602083611d0f565b6000808352366020840137565b908092918237016000815290565b90602083828152019260208260051b82010193836000925b8484106123295750505050505090565b909192939495602080612351600193601f1986820301885261234b8b88611e22565b90611e53565b9801940194019294939190612319565b969261239c906123b0969561238e6104f69b999563ffffffff958c5260c060208d015260c08c0190610984565b918a830360408c0152611e53565b931660608701528583036080870152612301565b9160a0818403910152610984565b94959096939192936123ce61295b565b91841597881561263b575b6123e288612b8c565b976000891561263557506012850154612403906001600160a01b0316610672565b60405163cfa651f360e01b81526004810183905290602090829060249082905afa90811561078257600091612616575b50945b898061260e575b612569575b61244c3383612c8b565b61245582612ed6565b61245d612f0d565b996124678b61353b565b80612560575b1561254057601201546124b39594939291600091612493906001600160a01b0316610672565b60405180809981946316740b0b60e31b8352600483019190602083019252565b03915afa93841561078257611eab9b6103da9860208c976103cc99600091612525575b500151929c15612501575050955b604051631bee0bf560e11b60208201529889978c60248a01612361565b6103cc61251f9293610fc7610fba60405196879560208701916122f3565b956124e4565b61253a91503d806000833e610cb48183611d0f565b386124d6565b506103da9699506103cc94509061251f611eab9b8a9594939b36916120c6565b5085151561246d565b61257c612577368a8f6120c6565b614360565b6012820154612593906001600160a01b0316610672565b60206040518092635da1452f60e01b825281806125b4878a60048401612033565b03915afa908115610782576000916125ef575b506125d25750612442565b60405163c0c9447760e01b81529081906107e790600483016109a9565b612608915060203d6020116107c5576107b78183611d0f565b386125c7565b50851561243d565b61262f915060203d60201161077b5761076d8183611d0f565b38612433565b94612436565b61264e61264936888d6120c6565b6142f3565b6123d9565b90602060ff92600e61266361295b565b01836040519485938437820190815203019020541690565b6000805160206154ef8339815191528114801561277e575b8015612767575b8015612750575b8015612739575b8015612722575b801561270b575b80156126f4575b156126e0576126cb90611e74565b80156126da576104f690611d85565b50600090565b63332ed6cd60e21b60005260045260246000fd5b506000805160206155af83398151915281146126bd565b5060008051602061550f83398151915281146126b6565b5060008051602061558f83398151915281146126af565b5060008051602061552f83398151915281146126a8565b5060008051602061556f83398151915281146126a1565b5060008051602061554f833981519152811461269a565b506000805160206155cf8339815191528114612693565b926127c16104f695936127b3611f0e94608088526080880190610984565b908682036020880152610984565b908482036040860152610984565b9081602091031261024757516104f681611786565b916104f6926040815260018060a01b03835116604082015260208301511515606082015260408301516080820152606083015160a082015260c0612837608085015160e083850152610120840190610984565b9360a081015160e084015201516101008201526020818403910152610984565b634e487b7160e01b600052602160045260246000fd5b6003111561287757565b612857565b3560038110156102475790565b92906128c3926128a76128b59298969860c0875260c0870190610984565b908582036020870152610984565b908382036040850152610984565b926003851015612877576104f6946060830152608082015260a0818403910152610984565b926127c16104f69593611f0e938652608060208701526080860190610984565b6104f69392606092825260208201528160408201520190610984565b634e487b7160e01b600052601160045260246000fd5b60001981019190821161294957565b612924565b9190820391821161294957565b604051602081017f4d4b313ebcb0a912513922edb5e106a88014bff7fd8b7c782480f94eaff75a96815260208252612994604083611d0f565b9051902060ff191690565b9060008113156129e657506201517f1942014281116129495782106129c2575050565b69ffffffffffffffffffff9063142f6edf60e11b6000521660045260245260446000fd5b6338ee04a760e01b60005260045260246000fd5b9081602091031261024757516104f681611e17565b60ff166002039060ff821161294957565b60ff6001199116019060ff821161294957565b60ff16604d811161294957600a0a90565b90670de0b6b3a7640000820291808304670de0b6b3a7640000149015171561294957565b600181901b91906001600160ff1b0381160361294957565b8181029291811591840414171561294957565b8115612a9d570490565b634e487b7160e01b600052601260045260246000fd5b60046020612ac7610672600961066461295b565b60405163313ce56760e01b815292839182905afa90811561078257600091612b5d575b506000831315612b4757600260ff821610612b2b57612b216104f69392612b1b612b16612b2694612a20565b612a33565b90612a80565b612a44565b612a93565b612b416104f693612b1b612b16612b2694612a0f565b91612a44565b6338ee04a760e01b600052600483905260246000fd5b612b7f915060203d602011612b85575b612b778183611d0f565b8101906129fa565b38612aea565b503d612b6d565b612b9461295b565b6004810154612bab906001600160a01b0316610672565b604051634f558e7960e01b81526004810184905290602090829060249082905afa90811561078257600091612c6c575b50612c655760120154612bf6906001600160a01b0316610672565b604051634f558e7960e01b81526004810183905290602090829060249082905afa90811561078257600091612c46575b5015612c325750600190565b63701a312d60e11b60005260045260246000fd5b612c5f915060203d6020116107c5576107b78183611d0f565b38612c26565b5050600090565b612c85915060203d6020116107c5576107b78183611d0f565b38612bdb565b612c9361295b565b612c9c82612b8c565b15612d4d5760120154612cde91602091612cbe906001600160a01b0316610672565b60405180809581946331a9108f60e11b8352600483019190602083019252565b03915afa90811561078257600091612d2e575b50905b6001600160a01b0382811690821603612d0b575050565b63adabb9d960e01b6000526001600160a01b039081166004521660245260446000fd5b612d47915060203d602011611500576114f18183611d0f565b38612cf1565b60040154612d6a91602091612cbe906001600160a01b0316610672565b03915afa90811561078257600091612d84575b5090612cf4565b612d9d915060203d602011611500576114f18183611d0f565b38612d7d565b612dab61295b565b612db482612b8c565b15612e5f576012810154612dd0906001600160a01b0316610672565b6040516369b0777f60e11b81526004810184905290602090829060249082905afa8015610782576001926011612e1d928594600091612e40575b50915b0190600052602052604060002090565b541603612e275750565b631d11134b60e01b600052600452600160245260446000fd5b612e59915060203d60201161077b5761076d8183611d0f565b38612e0a565b6004810154612e76906001600160a01b0316610672565b6040516369b0777f60e11b81526004810184905290602090829060249082905afa8015610782576001926011612e1d928594600091612eb7575b5091612e0d565b612ed0915060203d60201161077b5761076d8183611d0f565b38612eb0565b612ede61295b565b50600280612eeb83614993565b1603612ef45750565b631d11134b60e01b600052600452600260245260446000fd5b600d612f1761295b565b0180546000198114612949576104f6916001820190556040516020810191438352466040830152606082015260608152612f52608082611d0f565b51902060016fffffffffffffffffffffffffffffffff821160071b6001600160401b0383821c1160061b1763ffffffff83821c1160051b1761ffff83821c1160041b1760ff83612fa28360031c90565b921c11170190614c0e565b3d15612fd8573d90612fbe82611f2b565b91612fcc6040519384611d0f565b82523d6000602084013e565b606090565b612fe561295b565b90613016600161301060008051602061552f833981519152600a860190600052602052604060002090565b54612a80565b9182156131f7576009810154613034906001600160a01b0316610672565b604051633fabe5a360e21b815260a081600481855afa8015610782576000906000906000906131c6575b82935061306f90826130759461299f565b86612ab3565b3403613117575b5050600b01546001600160a01b031680156131065760009081908190819034906130ae906001600160a01b0316610672565b5af16130b8612fad565b50156130f5577fe639fa7b09c9280a0841241aaca60ee854005312117168919f20f2cc4b96673c916130f06040519283923484612908565b0390a1565b6312171d8360e31b60005260046000fd5b63d92e233d60e01b60005260046000fd5b61312090614a32565b604051639a6fc8f560e01b815269ffffffffffffffffffff821660048201529160a090839060249082905afa9182156107825761317492600091600091613199575b508161316e929361299f565b84612ab3565b803403613181578061307c565b631e0bd6c160e31b6000526004523460245260446000fd5b61316e92506131b7915060a03d60a011611e1057611dfa8183611d0f565b50919350909150829050613162565b5050506130756131e761306f9260a03d60a011611e1057611dfa8183611d0f565b509294508493509091905061305e565b5050503461320157565b636a71130f60e01b60005260008051602061552f833981519152600452600160245260446000fd5b61323161295b565b9061325c600161301060008051602061556f833981519152600a860190600052602052604060002090565b91821561327a576009810154613034906001600160a01b0316610672565b5050503461328457565b636a71130f60e01b60005260008051602061556f833981519152600452600160245260446000fd5b6132b461295b565b906132df600161301060008051602061554f833981519152600a860190600052602052604060002090565b9182156132fd576009810154613034906001600160a01b0316610672565b5050503461330757565b636a71130f60e01b60005260008051602061554f833981519152600452600160245260446000fd5b9061333861295b565b613361836130106000805160206154ef833981519152600a850190600052602052604060002090565b92831561338057506009810154613034906001600160a01b0316610672565b925050503461338c5750565b636a71130f60e01b6000526000805160206154ef83398151915260045260245260446000fd5b6133ba61295b565b906133e560016130106000805160206155cf833981519152600a860190600052602052604060002090565b918215613403576009810154613034906001600160a01b0316610672565b5050503461340d57565b636a71130f60e01b6000526000805160206155cf833981519152600452600160245260446000fd5b61343d61295b565b9061346860016130106000805160206155af833981519152600a860190600052602052604060002090565b918215613486576009810154613034906001600160a01b0316610672565b5050503461349057565b636a71130f60e01b6000526000805160206155af833981519152600452600160245260446000fd5b6134c061295b565b906134eb600161301060008051602061550f833981519152600a860190600052602052604060002090565b918215613509576009810154613034906001600160a01b0316610672565b5050503461351357565b636a71130f60e01b60005260008051602061550f833981519152600452600160245260446000fd5b61354361295b565b9061356e600161301060008051602061558f833981519152600a860190600052602052604060002090565b91821561358c576009810154613034906001600160a01b0316610672565b5050503461359657565b636a71130f60e01b60005260008051602061558f833981519152600452600160245260446000fd5b604051606091906135cf8382611d0f565b6002815291601f19018260005b8281106135e857505050565b8060606020809385010152016135dc565b6040805190919061360a8382611d0f565b6001815291601f19018260005b82811061362357505050565b806060602080938501015201613617565b8051600110156120c15760400190565b8051156120c15760200190565b80518210156120c15760209160051b010190565b90600182811c92168015613695575b602083101461367f57565b634e487b7160e01b600052602260045260246000fd5b91607f1691613674565b90604051918260008254926136b384613665565b808452936001811690811561371f57506001146136d8575b50611eab92500383611d0f565b90506000929192526020600020906000915b818310613703575050906020611eab92820101386136cb565b60209193508060019154838589010152019101909184926136ea565b905060209250611eab94915060ff191682840152151560051b820101386136cb565b9080602083519182815201916020808360051b8301019401926000915b83831061376d57505050505090565b909192939460208061378b600193601f198682030187528951610984565b9701930193019193929061375e565b92949391608084526000918054906137b182613665565b9182608088015260018116908160001461383757506001146137f8575b5050946127c1826104f69697866137ea95036020880152610984565b916060818403910152613741565b600090815260208120929350915b818310613820575050830160a001906127c16104f66137ce565b805460a08488010152602090920191600101613806565b60ff191660a08881019190915292151560051b870190920193506127c191506104f690506137ce565b81519192916139455760209192506103cc61389f61387c6135f9565b945b604051633b325fc560e01b8682015260248101919091529182906044820190565b6138a884613644565b526138b283613644565b506138bb61295b565b60038101546138d2906001600160a01b0316610672565b9060006138f860058301926138f260066138eb8661369f565b920161369f565b90614a73565b9161391960405197889687958694631f3f66f760e21b86526004860161379a565b03925af180156107825761392a5750565b6139429060203d60201161077b5761076d8183611d0f565b50565b6103cc61389f6020936103cc61397861395c6135be565b9760405192839163f23f601b60e01b8a840152602483016109a9565b61398187613634565b5261398b86613634565b5061387e565b90916121b46104f69360408452604084019061108e565b6139b061295b565b6139ba60016122c0565b916139c86040519384611d0f565b600183526139d660016122c0565b6020840190601f19013682378351156120c1575260040154613a00906001600160a01b0316610672565b91823b1561024757613a2c926000928360405180968195829463a4362b3960e01b845260048401613991565b03925af1801561078257613a3d5750565b80610c416000611eab93611d0f565b90613a5682611f2b565b613a636040519182611d0f565b8281528092613a74601f1991611f2b565b0190602036910137565b8060009172184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b821015613bb3575b806d04ee2d6d415b85acef8100000000600a921015613b97575b662386f26fc10000811015613b82575b6305f5e100811015613b70575b612710811015613b60575b6064811015613b51575b1015613b46575b613b316021613b0560018501613a4c565b938401015b60001901916f181899199a1a9b1b9c1cb0b131b232b360811b600a82061a8353600a900490565b8015613b4157613b319091613b0a565b505090565b600190910190613af4565b60029060649004930192613aed565b6004906127109004930192613ae3565b6008906305f5e1009004930192613ad8565b601090662386f26fc100009004930192613acb565b6020906d04ee2d6d415b85acef81000000009004930192613abb565b506040915072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b8104613aa1565b428110613be05750565b633bd4ab4160e01b6000526004524260245260446000fd5b6002613c0261295b565b01908060005281602052600160ff60406000205416151514613c36576000526020526040600020600160ff19825416179055565b632472ad4160e21b60005260045260246000fd5b90613c54826122c0565b613c616040519182611d0f565b8281528092613a74601f19916122c0565b60405190613c81604083611d0f565b601f82527f4e616d65496e666f28737472696e6720736c642c737472696e6720746c6429006020830152565b613cb5613c72565b604051613ce360208281613cd28183019687815193849201610961565b81010301601f198101835282611d0f565b51902090565b604051613cf7608082611d0f565b605a815260208101907f546f6b656e697a6174696f6e566f7563686572284e616d65496e666f5b5d206e82527f616d65732c75696e74323536206e6f6e63652c75696e7432353620657870697260408201527f657341742c61646472657373206f776e657241646472657373290000000000006060820152613ce3613d7a613c72565b916020604051938492613d9583850197889251928391610961565b8301613da982518093858085019101610961565b010103601f198101835282611d0f565b805160209091019060005b818110613dd15750505090565b8251845260209384019390920191600101613dc4565b613dfb613df48280612054565b9050613c4a565b9060005b613e098280612054565b9050811015613e7c5780613e26610f75600193610f6f8680612054565b613e2e613cad565b815180516020918201209281015180519082012060408051928301938452820193909352606081019290925290613e6881608081016103cc565b519020613e758286613651565b5201613dff565b50613ce3613e88613ce9565b92604051613e9e816103cc602082018095613db9565b5190206103cc602084013593613ebb60606040830135920161204a565b604080516020810198895290810194909452606084019590955260808301526001600160a01b0390931660a082015291829060c0820190565b604290613eff61535a565b613f076153c4565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a08152613f5860c082611d0f565b519020906040519161190160f01b8352600283015260228201522090565b613f88613f8e92613f979436916120c6565b90614caf565b90929192614cf5565b613fba81613fa361295b565b9060018060a01b0316600052602052604060002090565b54908115613fc6575090565b63bf18af4360e01b60009081526001600160a01b0391909116600452602490fd5b613ff081614d77565b15613ff85750565b604051630a9644cf60e01b8152602060048201529081906107e7906024830190610984565b61402681615197565b1561402e5750565b604051637f19f48d60e01b8152602060048201529081906107e7906024830190610984565b8051600110156120c15760210190565b8051600210156120c15760220190565b8051600310156120c15760230190565b9081518110156120c1570160200190565b8015612949576000190190565b6000815191825b6140b3575b50905090565b6000198301838111612949575b80151580614115575b156140dc576140d790614094565b6140c0565b916140f56140ee846140fb949661294e565b8486614b42565b90614bce565b9080156000036140ad5761410e9061293a565b91826140a8565b50601760f91b6001600160f81b03196141476141396141338561293a565b86614083565b516001600160f81b03191690565b1614156140c9565b6040519061415e604083611d0f565b600682526565697031353560d01b6020830152565b6001600160a01b03168061419761419261418d6014612a68565b614337565b613a4c565b9160306141a384613644565b5360786141af84614053565b536141c26141bd6014612a68565b614345565b60018111614277575061425e57506028602282012060601c6029905b600182116141eb57505090565b614209816007600f61420f9416118061423c575b6142155760041c90565b91614094565b906141de565b600160fd1b6142276141398688614083565b1860001a6142358587614083565b5360041c90565b50606060ff614257614251614139888a614083565b60f81c90565b16116141ff565b63e22e27eb60e01b600052600452601460245260446000fd5b90600f811660108110156120c1576142ad91614209916f181899199a1a9b1b9c1cb0b131b232b360811b901a6142358588614083565b6141c2565b604051906142c1604083611d0f565b6004825263444f4d4160e01b6020830152565b604051906142e3604083611d0f565b60018252603160f81b6020830152565b6142fc81615285565b156143045750565b60405163d4c19e3b60e01b8152602060048201529081906107e7906024830190610984565b90601f820180921161294957565b906002820180921161294957565b906001820180921161294957565b9190820180921161294957565b805180156143fb57805b614372575090565b601760f91b6143966143896141396141338561293a565b6001600160f81b03191690565b146143aa576143a490614094565b8061436a565b6143ba614192828495945161294e565b9060005b82518110156143f457806143e06141396143da60019486614353565b88614083565b60001a6143ed8286614083565b53016143be565b5090925050565b5090565b9161440861295b565b61441461063a85612b8c565b614568576012015461442e906001600160a01b0316610672565b6040516364f1992360e01b815260048101859052602081602481855afa90811561078257600091614549575b5061453357604051633329dc9d60e11b81526004810185905290602090829060249082905afa90811561078257600091614514575b504281106144fb57506144ac916144a79136916120c6565b613fe7565b6002808316036144e25781806144c183614993565b16036144cb575050565b631d11134b60e01b60005260045260245260446000fd5b631d11134b60e01b600052600452600260245260446000fd5b636581bb3f60e11b600052600484905260245260446000fd5b61452d915060203d60201161077b5761076d8183611d0f565b3861448f565b63a9de8c6f60e01b600052600484905260246000fd5b614562915060203d6020116107c5576107b78183611d0f565b3861445a565b635a55e97160e11b600052600484905260246000fd5b61458a6020820161287c565b600381101561287757613ce36040516145a460a082611d0f565b6062815261742960f01b608060208301927f50726f6f664f66436f6e7461637473566f75636865722875696e74323536207284527f656769737472616e7448616e646c652c75696e74382070726f6f66536f75726360408201527f652c75696e74323536206e6f6e63652c75696e743235362065787069726573416060820152015260405161463760208201809361093b565b60628152614646608282611d0f565b519020604080516020810192835285358183015260ff9094166060858101919091529085013560808501529093013560a08301528160c081016103cc565b613f88613f8e926146969436916120c6565b60ff6146be8260016146a661295b565b019060018060a01b0316600052602052604060002090565b541615613fc65750565b906146d161295b565b906146de61063a84612b8c565b61483c5760126146f5926106646106729386612c8b565b6040516364f1992360e01b815260048101839052909190602081602481865afa9081156107825760009161481d575b5061480957604051633329dc9d60e11b815260048101829052602081602481865afa908115610782576000916147ea575b504281106147d0575060405163a34e303d60e01b81526004810182905291602090839060249082905afa918215610782576000926147af575b5081614798575050565b63f9f7458560e01b60005260045260245260446000fd5b6147c991925060203d60201161077b5761076d8183611d0f565b903861478e565b636581bb3f60e11b60005260049190915260245260446000fd5b614803915060203d60201161077b5761076d8183611d0f565b38614755565b63a9de8c6f60e01b60005260045260246000fd5b614836915060203d6020116107c5576107b78183611d0f565b38614724565b635a55e97160e11b600052600483905260246000fd5b9061485b61295b565b9061486583612b8c565b61497d576148739083612c8b565b60028061487f84614993565b1603614962576004015461489b906001600160a01b0316610672565b6040516364f1992360e01b815260048101839052909190602081602481865afa90811561078257600091614943575b5061480957604051633329dc9d60e11b81526004810182905291602090839060249082905afa91821561078257600092614922575b5042821061490b575050565b636581bb3f60e11b60005260045260245260446000fd5b61493c91925060203d60201161077b5761076d8183611d0f565b90386148ff565b61495c915060203d6020116107c5576107b78183611d0f565b386148ca565b631d11134b60e01b6000526004829052600260245260446000fd5b630bfffc8160e31b600052600483905260246000fd5b61499b61295b565b6149a482612b8c565b15614a1557601201546149e6916020916149c6906001600160a01b0316610672565b60405180809581946332b9c2b360e21b8352600483019190602083019252565b03915afa908115610782576000916149fc575090565b6104f6915060203d60201161077b5761076d8183611d0f565b600401546149e6916020916149c6906001600160a01b0316610672565b6001600160401b03811660018111156143fb57600019016001600160401b038111612949576001600160401b031669ffff0000000000000000919091161790565b6001611eab9193929360206040519582614a968894518092858088019101610961565b8301601d60f91b83820152614ab48251809385602185019101610961565b01010301601f198101845283611d0f565b15614acc57565b60405162461bcd60e51b815260206004820152600e60248201526d736c6963655f6f766572666c6f7760901b6044820152606490fd5b15614b0957565b60405162461bcd60e51b8152602060048201526011602482015270736c6963655f6f75744f66426f756e647360781b6044820152606490fd5b91614b5781614b5081614329565b1015614ac5565b614b6d8351614b668385614353565b1115614b02565b80614b8657505050604051600081526020810160405290565b60405192601f821692831560051b80858701019484860193010101905b808410614bbb5750508252601f01601f191660405290565b9092602080918551815201930190614ba3565b90604051614bec60208281613cd28183019687815193849201610961565b5190206040519060208201928352604082015260408152613ce3606082611d0f565b9081614c1f61419261418d84612a68565b926030614c2b85613644565b536078614c3785614053565b53614c446141bd84612a68565b60018111614c6d5750614c5657505090565b63e22e27eb60e01b60005260045260245260446000fd5b90600f81169060108210156120c157614caa916f181899199a1a9b1b9c1cb0b131b232b360811b901a614ca08488614083565b5360041c91614094565b614c44565b8151919060418303614ce057614cd992506020820151906060604084015193015160001a90615409565b9192909190565b505060009160029190565b6004111561287757565b614cfe81614ceb565b80614d07575050565b614d1081614ceb565b60018103614d295763f645eedf60e01b60005260046000fd5b614d3281614ceb565b60028103614d4f575063fce698f760e01b60005260045260246000fd5b80614d5b600392614ceb565b14614d635750565b6335e2f38360e21b60005260045260246000fd5b805180158015614f71575b612c6557600481119081614f4d575b81614f29575b81614f05575b81614ee1575b60005b818110614db65750505050600190565b614dc66143896141398387614083565b606160f81b81101580614ed3575b8015614eb6575b8015614ea9575b8015614ea1575b15614e795781158015614e90575b80614e83575b614e7957600282149081614e6b575b5080614e5a575b80614e35575b614e26575b600101614da6565b82614e1e575b50505050600090565b50602d60f81b6001600160f81b0319614e536141396143da85614345565b1614614e19565b5081614e6582614345565b10614e13565b602d60f81b14905038614e0c565b5050505050600090565b50602d60f81b8114614dfd565b50614e9a8361293a565b8214614df7565b506000614de9565b50602d60f81b8114614de2565b50600360fc1b8110801590614ddb5750603960f81b811115614ddb565b50603d60f91b811115614dd4565b9050602d60f81b6001600160f81b0319614efd61413985614073565b161490614da3565b9050602d60f81b6001600160f81b0319614f2161413985614063565b161490614d9d565b9050603760f91b6001600160f81b0319614f4561413985614053565b161490614d97565b9050600f60fb1b6001600160f81b0319614f6961413985613644565b161490614d91565b50603f8111614d82565b8051918215801561518d575b61518557600483119182615161575b8261513d575b82615119575b826150f5575b60005b848110614fbc575050505050600190565b614fcc6143896141398385614083565b606160f81b811015806150e7575b80156150ca575b80156150bd575b80156150a8575b1561507f5781158015615097575b8061508a575b61507f57600282149081615071575b5080615060575b8061503b575b61502c575b600101614fab565b83615024575050505050600090565b50602d60f81b6001600160f81b031961505961413961413385614345565b161461501f565b508461506b82614345565b10615019565b602d60f81b14905038615012565b505050505050600090565b50602d60f81b8114615003565b506150a18661293a565b8214614ffd565b50838015614fef5750605f60f81b8114614fef565b50602d60f81b8114614fe8565b50600360fc1b8110801590614fe15750603960f81b811115614fe1565b50603d60f91b811115614fda565b9150602d60f81b6001600160f81b031961511161413985614073565b161491614fa8565b9150602d60f81b6001600160f81b031961513561413985614063565b161491614fa2565b9150603760f91b6001600160f81b031961515961413985614053565b161491614f9c565b9150600f60fb1b6001600160f81b031961517d61413985613644565b161491614f96565b505050600090565b50603f8311614f87565b8051908115801561527b575b612c6557601760f91b6001600160f81b03196151c161413984613644565b16148015615257575b612c655760009060005b83811061520257506151ef6151f49361063a93600093615492565b614f7b565b6151fd57600190565b600090565b601760f91b6152176143896141398486614083565b14615225575b6001016151d4565b91808314614e2c5761063a60006151ef856152409486615492565b61518557600161524f83614345565b92905061521d565b50601760f91b61527561438961413961526f8661293a565b85614083565b146151ca565b5060fd82116151a3565b80519081158015615350575b612c6557601760f91b6001600160f81b03196152af61413984613644565b16148015615332575b612c655760009060005b8381106152dd57506151ef6151f49361063a93600193615492565b601760f91b6152f26143896141398486614083565b14615300575b6001016152c2565b91808314614e2c5761063a60016151ef8561531b9486615492565b61518557600161532a83614345565b9290506152f8565b50601760f91b61534a61438961413961526f8661293a565b146152b8565b5060fd8211615291565b6153626142b2565b8051908115615372576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054801561539f5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b6153cc6142d4565b80519081156153dc576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10154801561539f5790565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615486579160209360809260ff60009560405194855216868401526040830152606082015282805260015afa15610782576000516001600160a01b0381161561547a5790600090600090565b50600090600190600090565b50505060009160039190565b91818103818111612949576154a690613a4c565b92825b8281106154b7575050505090565b6001600160f81b03196154ca8284614083565b511690848103818111612949576154e760019360001a9188614083565b53016154a956fe41274d8380e68ced671e39b24773a9a28190d6ae058a9a0a57272be30902055c4a2248149817dde7dfda12ec5979900c9a0aba1d5ecdd54d38762a0cc40039961ee9a1c13af222b9e4d98567d0238a554fbf843b89f4720f3aa34731720cdffa08fb31c3e81624356c3314088aa971b73bcc82d22bc3e3b184b4593077ae3278add9df460364fdc361c17268aded1a218d04bba51bfce5119914d05b8bd10ababbecd033b99e35d8972b316640f0b13a68063e02065b4e964541d3498c941657272cf3b1b4a64c268809c75d1290f1c3ac8c1e2df8ebefee53369069b86f0649a19646f5f88bada8fee59cab6efcdf841c1797d1344642f9af3a4e0415431506a26469706673582212205d03e947712de15fd7c777d5d9ce651c353462302ef3f97fcafe502bcfa078fc64736f6c634300081c0033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
0xAd8FE2350f85bcbA946b69e2b1CA867AB2886070
Loading...
Loading
Loading...
Loading
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.