ETH Price: $4,409.80 (-1.51%)

Transaction Decoder

Block:
23233497 at Aug-27-2025 03:57:47 PM +UTC
Transaction Fee:
0.000266110879861425 ETH $1.17
Gas Used:
85,615 Gas / 3.108227295 Gwei

Emitted Events:

263 0x39df8b6e0a2d04960fa32f7352a512971f599efd.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000025878213e713b4cc75a4a776c54f11a53c6f0dc1, 0x000000000000000000000000000000000000000000000000000000000000dead, 0x00000000000000000000000000000000000000000000000000000000000000a3 )

Account State Difference:

  Address   Before After State Difference Code
0x25878213...53c6f0dc1
0.017839477276528927 Eth
Nonce: 28
0.017573366396667502 Eth
Nonce: 29
0.000266110879861425
0x39Df8B6e...71F599EfD
(Titan Builder)
9.566586233185449442 Eth9.566671848185449442 Eth0.000085615

Execution Trace

0x39df8b6e0a2d04960fa32f7352a512971f599efd.23b872dd( )
  • CreatorTokenTransferValidator.validateTransfer( caller=0x25878213e713b4cC75a4A776C54f11A53c6f0dc1, from=0x25878213e713b4cC75a4A776C54f11A53c6f0dc1, to=0x000000000000000000000000000000000000dEaD, tokenId=163 )
    • CreatorTokenTransferValidator.validateTransferDelegateCall( authorizerCheckType=1, collection=0x39Df8B6e0A2D04960Fa32f7352A512971F599EfD, caller=0x25878213e713b4cC75a4A776C54f11A53c6f0dc1, from=0x25878213e713b4cC75a4A776C54f11A53c6f0dc1, to=0x000000000000000000000000000000000000dEaD, tokenId=163, amount=0 ) => ( errorSelector=System.Byte[], tokenType=0 )
      • RulesetWhitelist.validateTransfer( authorizerCheckType=1, collection=0x39Df8B6e0A2D04960Fa32f7352A512971F599EfD, caller=0x25878213e713b4cC75a4A776C54f11A53c6f0dc1, from=0x25878213e713b4cC75a4A776C54f11A53c6f0dc1, to=0x000000000000000000000000000000000000dEaD, tokenId=163, amount=0 ) => ( System.Byte[] )
        File 1 of 2: CreatorTokenTransferValidator
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155.sol)
        pragma solidity ^0.8.0;
        import "../token/ERC1155/IERC1155.sol";
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Interface of the ERC1271 standard signature validation method for
         * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
         *
         * _Available since v4.1._
         */
        interface IERC1271 {
            /**
             * @dev Should return whether the signature provided is valid for the provided data
             * @param hash      Hash of the data to be signed
             * @param signature Signature byte array associated with _data
             */
            function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)
        pragma solidity ^0.8.0;
        import "../token/ERC20/IERC20.sol";
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol)
        pragma solidity ^0.8.0;
        import "../token/ERC721/IERC721.sol";
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)
        pragma solidity ^0.8.0;
        import "../../utils/introspection/IERC165.sol";
        /**
         * @dev Required interface of an ERC1155 compliant contract, as defined in the
         * https://eips.ethereum.org/EIPS/eip-1155[EIP].
         *
         * _Available since v3.1._
         */
        interface IERC1155 is IERC165 {
            /**
             * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
             */
            event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
            /**
             * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
             * transfers.
             */
            event TransferBatch(
                address indexed operator,
                address indexed from,
                address indexed to,
                uint256[] ids,
                uint256[] values
            );
            /**
             * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
             * `approved`.
             */
            event ApprovalForAll(address indexed account, address indexed operator, bool approved);
            /**
             * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
             *
             * If an {URI} event was emitted for `id`, the standard
             * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
             * returned by {IERC1155MetadataURI-uri}.
             */
            event URI(string value, uint256 indexed id);
            /**
             * @dev Returns the amount of tokens of token type `id` owned by `account`.
             *
             * Requirements:
             *
             * - `account` cannot be the zero address.
             */
            function balanceOf(address account, uint256 id) external view returns (uint256);
            /**
             * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
             *
             * Requirements:
             *
             * - `accounts` and `ids` must have the same length.
             */
            function balanceOfBatch(
                address[] calldata accounts,
                uint256[] calldata ids
            ) external view returns (uint256[] memory);
            /**
             * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
             *
             * Emits an {ApprovalForAll} event.
             *
             * Requirements:
             *
             * - `operator` cannot be the caller.
             */
            function setApprovalForAll(address operator, bool approved) external;
            /**
             * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
             *
             * See {setApprovalForAll}.
             */
            function isApprovedForAll(address account, address operator) external view returns (bool);
            /**
             * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
             *
             * Emits a {TransferSingle} event.
             *
             * Requirements:
             *
             * - `to` cannot be the zero address.
             * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
             * - `from` must have a balance of tokens of type `id` of at least `amount`.
             * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
             * acceptance magic value.
             */
            function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
            /**
             * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
             *
             * Emits a {TransferBatch} event.
             *
             * Requirements:
             *
             * - `ids` and `amounts` must have the same length.
             * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
             * acceptance magic value.
             */
            function safeBatchTransferFrom(
                address from,
                address to,
                uint256[] calldata ids,
                uint256[] calldata amounts,
                bytes calldata data
            ) external;
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Interface of the ERC20 standard as defined in the EIP.
         */
        interface IERC20 {
            /**
             * @dev Emitted when `value` tokens are moved from one account (`from`) to
             * another (`to`).
             *
             * Note that `value` may be zero.
             */
            event Transfer(address indexed from, address indexed to, uint256 value);
            /**
             * @dev Emitted when the allowance of a `spender` for an `owner` is set by
             * a call to {approve}. `value` is the new allowance.
             */
            event Approval(address indexed owner, address indexed spender, uint256 value);
            /**
             * @dev Returns the amount of tokens in existence.
             */
            function totalSupply() external view returns (uint256);
            /**
             * @dev Returns the amount of tokens owned by `account`.
             */
            function balanceOf(address account) external view returns (uint256);
            /**
             * @dev Moves `amount` tokens from the caller's account to `to`.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transfer(address to, uint256 amount) external returns (bool);
            /**
             * @dev Returns the remaining number of tokens that `spender` will be
             * allowed to spend on behalf of `owner` through {transferFrom}. This is
             * zero by default.
             *
             * This value changes when {approve} or {transferFrom} are called.
             */
            function allowance(address owner, address spender) external view returns (uint256);
            /**
             * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * IMPORTANT: Beware that changing an allowance with this method brings the risk
             * that someone may use both the old and the new allowance by unfortunate
             * transaction ordering. One possible solution to mitigate this race
             * condition is to first reduce the spender's allowance to 0 and set the
             * desired value afterwards:
             * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
             *
             * Emits an {Approval} event.
             */
            function approve(address spender, uint256 amount) external returns (bool);
            /**
             * @dev Moves `amount` tokens from `from` to `to` using the
             * allowance mechanism. `amount` is then deducted from the caller's
             * allowance.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(address from, address to, uint256 amount) external returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
        pragma solidity ^0.8.0;
        import "../../utils/introspection/IERC165.sol";
        /**
         * @dev Required interface of an ERC721 compliant contract.
         */
        interface IERC721 is IERC165 {
            /**
             * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
             */
            event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
            /**
             * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
             */
            event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
            /**
             * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
             */
            event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
            /**
             * @dev Returns the number of tokens in ``owner``'s account.
             */
            function balanceOf(address owner) external view returns (uint256 balance);
            /**
             * @dev Returns the owner of the `tokenId` token.
             *
             * Requirements:
             *
             * - `tokenId` must exist.
             */
            function ownerOf(uint256 tokenId) external view returns (address owner);
            /**
             * @dev Safely transfers `tokenId` token from `from` to `to`.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must exist and be owned by `from`.
             * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
             * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
             *
             * Emits a {Transfer} event.
             */
            function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
            /**
             * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
             * are aware of the ERC721 protocol to prevent tokens from being forever locked.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must exist and be owned by `from`.
             * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
             * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
             *
             * Emits a {Transfer} event.
             */
            function safeTransferFrom(address from, address to, uint256 tokenId) external;
            /**
             * @dev Transfers `tokenId` token from `from` to `to`.
             *
             * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
             * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
             * understand this adds an external call which potentially creates a reentrancy vulnerability.
             *
             * Requirements:
             *
             * - `from` cannot be the zero address.
             * - `to` cannot be the zero address.
             * - `tokenId` token must be owned by `from`.
             * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(address from, address to, uint256 tokenId) external;
            /**
             * @dev Gives permission to `to` to transfer `tokenId` token to another account.
             * The approval is cleared when the token is transferred.
             *
             * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
             *
             * Requirements:
             *
             * - The caller must own the token or be an approved operator.
             * - `tokenId` must exist.
             *
             * Emits an {Approval} event.
             */
            function approve(address to, uint256 tokenId) external;
            /**
             * @dev Approve or remove `operator` as an operator for the caller.
             * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
             *
             * Requirements:
             *
             * - The `operator` cannot be the caller.
             *
             * Emits an {ApprovalForAll} event.
             */
            function setApprovalForAll(address operator, bool approved) external;
            /**
             * @dev Returns the account approved for `tokenId` token.
             *
             * Requirements:
             *
             * - `tokenId` must exist.
             */
            function getApproved(uint256 tokenId) external view returns (address operator);
            /**
             * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
             *
             * See {setApprovalForAll}
             */
            function isApprovedForAll(address owner, address operator) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Provides information about the current execution context, including the
         * sender of the transaction and its data. While these are generally available
         * via msg.sender and msg.data, they should not be accessed in such a direct
         * manner, since when dealing with meta-transactions the account sending and
         * paying for execution may not be the actual sender (as far as an application
         * is concerned).
         *
         * This contract is only required for intermediate, library-like contracts.
         */
        abstract contract Context {
            function _msgSender() internal view virtual returns (address) {
                return msg.sender;
            }
            function _msgData() internal view virtual returns (bytes calldata) {
                return msg.data;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
        pragma solidity ^0.8.0;
        import "./math/Math.sol";
        import "./math/SignedMath.sol";
        /**
         * @dev String operations.
         */
        library Strings {
            bytes16 private constant _SYMBOLS = "0123456789abcdef";
            uint8 private constant _ADDRESS_LENGTH = 20;
            /**
             * @dev Converts a `uint256` to its ASCII `string` decimal representation.
             */
            function toString(uint256 value) internal pure returns (string memory) {
                unchecked {
                    uint256 length = Math.log10(value) + 1;
                    string memory buffer = new string(length);
                    uint256 ptr;
                    /// @solidity memory-safe-assembly
                    assembly {
                        ptr := add(buffer, add(32, length))
                    }
                    while (true) {
                        ptr--;
                        /// @solidity memory-safe-assembly
                        assembly {
                            mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                        }
                        value /= 10;
                        if (value == 0) break;
                    }
                    return buffer;
                }
            }
            /**
             * @dev Converts a `int256` to its ASCII `string` decimal representation.
             */
            function toString(int256 value) internal pure returns (string memory) {
                return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
            }
            /**
             * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
             */
            function toHexString(uint256 value) internal pure returns (string memory) {
                unchecked {
                    return toHexString(value, Math.log256(value) + 1);
                }
            }
            /**
             * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
             */
            function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                bytes memory buffer = new bytes(2 * length + 2);
                buffer[0] = "0";
                buffer[1] = "x";
                for (uint256 i = 2 * length + 1; i > 1; --i) {
                    buffer[i] = _SYMBOLS[value & 0xf];
                    value >>= 4;
                }
                require(value == 0, "Strings: hex length insufficient");
                return string(buffer);
            }
            /**
             * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
             */
            function toHexString(address addr) internal pure returns (string memory) {
                return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
            }
            /**
             * @dev Returns true if the two strings are equal.
             */
            function equal(string memory a, string memory b) internal pure returns (bool) {
                return keccak256(bytes(a)) == keccak256(bytes(b));
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol)
        pragma solidity ^0.8.0;
        import "../Strings.sol";
        /**
         * @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,
                InvalidSignatureV // Deprecated in v4.8
            }
            function _throwError(RecoverError error) private pure {
                if (error == RecoverError.NoError) {
                    return; // no error: do nothing
                } else if (error == RecoverError.InvalidSignature) {
                    revert("ECDSA: invalid signature");
                } else if (error == RecoverError.InvalidSignatureLength) {
                    revert("ECDSA: invalid signature length");
                } else if (error == RecoverError.InvalidSignatureS) {
                    revert("ECDSA: invalid signature 's' value");
                }
            }
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with
             * `signature` or error string. This address can then be used for verification purposes.
             *
             * The `ecrecover` EVM opcode 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 {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]
             *
             * _Available since v4.3._
             */
            function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                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.
                    /// @solidity memory-safe-assembly
                    assembly {
                        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);
                }
            }
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with
             * `signature`. This address can then be used for verification purposes.
             *
             * The `ecrecover` EVM opcode 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 {toEthSignedMessageHash} on it.
             */
            function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                (address recovered, RecoverError error) = tryRecover(hash, signature);
                _throwError(error);
                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[EIP-2098 short signatures]
             *
             * _Available since v4.3._
             */
            function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
                bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                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.
             *
             * _Available since v4.2._
             */
            function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
                (address recovered, RecoverError error) = tryRecover(hash, r, vs);
                _throwError(error);
                return recovered;
            }
            /**
             * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
             * `r` and `s` signature fields separately.
             *
             * _Available since v4.3._
             */
            function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
                // 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);
                }
                // 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);
                }
                return (signer, RecoverError.NoError);
            }
            /**
             * @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) = tryRecover(hash, v, r, s);
                _throwError(error);
                return recovered;
            }
            /**
             * @dev Returns an Ethereum Signed Message, created from a `hash`. This
             * produces hash corresponding to the one signed with the
             * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
             * JSON-RPC method as part of EIP-191.
             *
             * See {recover}.
             */
            function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) {
                // 32 is the length in bytes of hash,
                // enforced by the type signature above
                /// @solidity memory-safe-assembly
                assembly {
                    mstore(0x00, "\\x19Ethereum Signed Message:\
        32")
                    mstore(0x1c, hash)
                    message := keccak256(0x00, 0x3c)
                }
            }
            /**
             * @dev Returns an Ethereum Signed Message, created from `s`. This
             * produces hash corresponding to the one signed with the
             * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
             * JSON-RPC method as part of EIP-191.
             *
             * See {recover}.
             */
            function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
        ", Strings.toString(s.length), s));
            }
            /**
             * @dev Returns an Ethereum Signed Typed Data, created from a
             * `domainSeparator` and a `structHash`. This produces hash corresponding
             * to the one signed with the
             * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
             * JSON-RPC method as part of EIP-712.
             *
             * See {recover}.
             */
            function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) {
                /// @solidity memory-safe-assembly
                assembly {
                    let ptr := mload(0x40)
                    mstore(ptr, "\\x19\\x01")
                    mstore(add(ptr, 0x02), domainSeparator)
                    mstore(add(ptr, 0x22), structHash)
                    data := keccak256(ptr, 0x42)
                }
            }
            /**
             * @dev Returns an Ethereum Signed Data with intended validator, created from a
             * `validator` and `data` according to the version 0 of EIP-191.
             *
             * See {recover}.
             */
            function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
                return keccak256(abi.encodePacked("\\x19\\x00", validator, data));
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * https://eips.ethereum.org/EIPS/eip-165[EIP].
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others ({ERC165Checker}).
         *
         * For an implementation, see {ERC165}.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Standard math utilities missing in the Solidity language.
         */
        library Math {
            enum Rounding {
                Down, // Toward negative infinity
                Up, // Toward infinity
                Zero // Toward zero
            }
            /**
             * @dev Returns the largest of two numbers.
             */
            function max(uint256 a, uint256 b) internal pure returns (uint256) {
                return a > b ? a : b;
            }
            /**
             * @dev Returns the smallest of two numbers.
             */
            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                return a < b ? a : b;
            }
            /**
             * @dev Returns the average of two numbers. The result is rounded towards
             * zero.
             */
            function average(uint256 a, uint256 b) internal pure returns (uint256) {
                // (a + b) / 2 can overflow.
                return (a & b) + (a ^ b) / 2;
            }
            /**
             * @dev Returns the ceiling of the division of two numbers.
             *
             * This differs from standard division with `/` in that it rounds up instead
             * of rounding down.
             */
            function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                // (a + b - 1) / b can overflow on addition, so we distribute.
                return a == 0 ? 0 : (a - 1) / b + 1;
            }
            /**
             * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
             * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
             * with further edits by Uniswap Labs also under MIT license.
             */
            function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
                unchecked {
                    // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                    // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                    // variables such that product = prod1 * 2^256 + prod0.
                    uint256 prod0; // Least significant 256 bits of the product
                    uint256 prod1; // Most significant 256 bits of the product
                    assembly {
                        let mm := mulmod(x, y, not(0))
                        prod0 := mul(x, y)
                        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                    }
                    // Handle non-overflow cases, 256 by 256 division.
                    if (prod1 == 0) {
                        // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                        // The surrounding unchecked block does not change this fact.
                        // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                        return prod0 / denominator;
                    }
                    // Make sure the result is less than 2^256. Also prevents denominator == 0.
                    require(denominator > prod1, "Math: mulDiv overflow");
                    ///////////////////////////////////////////////
                    // 512 by 256 division.
                    ///////////////////////////////////////////////
                    // Make division exact by subtracting the remainder from [prod1 prod0].
                    uint256 remainder;
                    assembly {
                        // Compute remainder using mulmod.
                        remainder := mulmod(x, y, denominator)
                        // Subtract 256 bit number from 512 bit number.
                        prod1 := sub(prod1, gt(remainder, prod0))
                        prod0 := sub(prod0, remainder)
                    }
                    // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                    // See https://cs.stackexchange.com/q/138556/92363.
                    // Does not overflow because the denominator cannot be zero at this stage in the function.
                    uint256 twos = denominator & (~denominator + 1);
                    assembly {
                        // Divide denominator by twos.
                        denominator := div(denominator, twos)
                        // Divide [prod1 prod0] by twos.
                        prod0 := div(prod0, twos)
                        // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                        twos := add(div(sub(0, twos), twos), 1)
                    }
                    // Shift in bits from prod1 into prod0.
                    prod0 |= prod1 * twos;
                    // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                    // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                    // four bits. That is, denominator * inv = 1 mod 2^4.
                    uint256 inverse = (3 * denominator) ^ 2;
                    // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                    // in modular arithmetic, doubling the correct bits in each step.
                    inverse *= 2 - denominator * inverse; // inverse mod 2^8
                    inverse *= 2 - denominator * inverse; // inverse mod 2^16
                    inverse *= 2 - denominator * inverse; // inverse mod 2^32
                    inverse *= 2 - denominator * inverse; // inverse mod 2^64
                    inverse *= 2 - denominator * inverse; // inverse mod 2^128
                    inverse *= 2 - denominator * inverse; // inverse mod 2^256
                    // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                    // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                    // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                    // is no longer required.
                    result = prod0 * inverse;
                    return result;
                }
            }
            /**
             * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
             */
            function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
                uint256 result = mulDiv(x, y, denominator);
                if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                    result += 1;
                }
                return result;
            }
            /**
             * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
             *
             * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
             */
            function sqrt(uint256 a) internal pure returns (uint256) {
                if (a == 0) {
                    return 0;
                }
                // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                //
                // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                //
                // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                //
                // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                uint256 result = 1 << (log2(a) >> 1);
                // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                // into the expected uint128 result.
                unchecked {
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    return min(result, a / result);
                }
            }
            /**
             * @notice Calculates sqrt(a), following the selected rounding direction.
             */
            function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = sqrt(a);
                    return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                }
            }
            /**
             * @dev Return the log in base 2, rounded down, of a positive value.
             * Returns 0 if given 0.
             */
            function log2(uint256 value) internal pure returns (uint256) {
                uint256 result = 0;
                unchecked {
                    if (value >> 128 > 0) {
                        value >>= 128;
                        result += 128;
                    }
                    if (value >> 64 > 0) {
                        value >>= 64;
                        result += 64;
                    }
                    if (value >> 32 > 0) {
                        value >>= 32;
                        result += 32;
                    }
                    if (value >> 16 > 0) {
                        value >>= 16;
                        result += 16;
                    }
                    if (value >> 8 > 0) {
                        value >>= 8;
                        result += 8;
                    }
                    if (value >> 4 > 0) {
                        value >>= 4;
                        result += 4;
                    }
                    if (value >> 2 > 0) {
                        value >>= 2;
                        result += 2;
                    }
                    if (value >> 1 > 0) {
                        result += 1;
                    }
                }
                return result;
            }
            /**
             * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
             * Returns 0 if given 0.
             */
            function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = log2(value);
                    return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                }
            }
            /**
             * @dev Return the log in base 10, rounded down, of a positive value.
             * Returns 0 if given 0.
             */
            function log10(uint256 value) internal pure returns (uint256) {
                uint256 result = 0;
                unchecked {
                    if (value >= 10 ** 64) {
                        value /= 10 ** 64;
                        result += 64;
                    }
                    if (value >= 10 ** 32) {
                        value /= 10 ** 32;
                        result += 32;
                    }
                    if (value >= 10 ** 16) {
                        value /= 10 ** 16;
                        result += 16;
                    }
                    if (value >= 10 ** 8) {
                        value /= 10 ** 8;
                        result += 8;
                    }
                    if (value >= 10 ** 4) {
                        value /= 10 ** 4;
                        result += 4;
                    }
                    if (value >= 10 ** 2) {
                        value /= 10 ** 2;
                        result += 2;
                    }
                    if (value >= 10 ** 1) {
                        result += 1;
                    }
                }
                return result;
            }
            /**
             * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
             * Returns 0 if given 0.
             */
            function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = log10(value);
                    return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
                }
            }
            /**
             * @dev Return the log in base 256, rounded down, of a positive value.
             * Returns 0 if given 0.
             *
             * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
             */
            function log256(uint256 value) internal pure returns (uint256) {
                uint256 result = 0;
                unchecked {
                    if (value >> 128 > 0) {
                        value >>= 128;
                        result += 16;
                    }
                    if (value >> 64 > 0) {
                        value >>= 64;
                        result += 8;
                    }
                    if (value >> 32 > 0) {
                        value >>= 32;
                        result += 4;
                    }
                    if (value >> 16 > 0) {
                        value >>= 16;
                        result += 2;
                    }
                    if (value >> 8 > 0) {
                        result += 1;
                    }
                }
                return result;
            }
            /**
             * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
             * Returns 0 if given 0.
             */
            function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = log256(value);
                    return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
        pragma solidity ^0.8.0;
        /**
         * @dev Standard signed math utilities missing in the Solidity language.
         */
        library SignedMath {
            /**
             * @dev Returns the largest of two signed numbers.
             */
            function max(int256 a, int256 b) internal pure returns (int256) {
                return a > b ? a : b;
            }
            /**
             * @dev Returns the smallest of two signed numbers.
             */
            function min(int256 a, int256 b) internal pure returns (int256) {
                return a < b ? a : b;
            }
            /**
             * @dev Returns the average of two signed numbers without overflow.
             * The result is rounded towards zero.
             */
            function average(int256 a, int256 b) internal pure returns (int256) {
                // Formula from the book "Hacker's Delight"
                int256 x = (a & b) + ((a ^ b) >> 1);
                return x + (int256(uint256(x) >> 255) & (a ^ b));
            }
            /**
             * @dev Returns the absolute unsigned value of a signed value.
             */
            function abs(int256 n) internal pure returns (uint256) {
                unchecked {
                    // must be unchecked in order to support `n = type(int256).min`
                    return uint256(n >= 0 ? n : -n);
                }
            }
        }
        //SPDX-License-Identifier: MIT
        pragma solidity ^0.8.24;
        /*
                                                             @@@@@@@@@@@@@@             
                                                            @@@@@@@@@@@@@@@@@@(         
                                                           @@@@@@@@@@@@@@@@@@@@@        
                                                          @@@@@@@@@@@@@@@@@@@@@@@@      
                                                                   #@@@@@@@@@@@@@@      
                                                                       @@@@@@@@@@@@     
                                    @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                                   @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                                  @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                                 @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                                @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                                @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                               @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                            @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                            @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                           @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                          @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                         @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                        @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                        @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
                    @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                   .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
                   @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
                  @@@@@@@@@@@@@@@                                                       
                 @@@@@@@@@@@@@@@                                                        
                @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
               @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
               @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
         
        * @title CollateralizedPausableFlags
        * @custom:version 1.0.0
        * @author Limit Break, Inc.
        * @description Collateralized Pausable Flags is an extension for contracts
        *              that require features to be pausable in the event of potential
        *              or actual threats without incurring a storage read overhead cost
        *              during normal operations by using contract starting balance as
        *              a signal for checking the paused state.
        *
        *              Using contract balance to enable checking paused state creates an
        *              economic penalty for developers that deploy code that can be 
        *              exploited as well as an economic incentive (recovery of collateral)
        *              for them to mitigate the threat.
        *
        *              Developers implementing Collateralized Pausable Flags should consider
        *              their risk mitigation strategy and ensure funds are readily available
        *              for pausing if ever necessary by setting an appropriate threshold 
        *              value and considering use of an escrow contract that can initiate the
        *              pause with funds.
        *
        *              There is no restriction on the depositor as this can be easily 
        *              circumvented through a `SELFDESTRUCT` opcode.
        *
        *              Developers must be aware of potential outflows from the contract that
        *              could reduce collateral below the pausable check threshold and protect
        *              against those methods when pausing is required.
        */
        abstract contract CollateralizedPausableFlags {
            /// @dev Emitted when the pausable flags are updated
            event PausableFlagsUpdated(uint256 previousFlags, uint256 newFlags);
            /// @dev Thrown when an execution path requires a flag to not be paused but it is paused
            error CollateralizedPausableFlags__Paused();
            /// @dev Thrown when an executin path requires a flag to be paused but it is not paused
            error CollateralizedPausableFlags__NotPaused();
            /// @dev Thrown when a call to withdraw funds fails
            error CollateralizedPausableFlags__WithdrawFailed();
            /// @dev Immutable variable that defines the native funds threshold before flags are checked
            uint256 private immutable nativeValueToCheckPauseState;
            /// @dev Flags for current pausable state, each bit is considered a separate flag
            uint256 private pausableFlags;
            /// @dev Immutable pointer for the _requireNotPaused function to use based on value threshold
            function(uint256) internal view immutable _requireNotPaused;
            /// @dev Immutable pointer for the _requirePaused function to use based on value threshold
            function(uint256) internal view immutable _requirePaused;
            /// @dev Immutable pointer for the _getPausableFlags function to use based on value threshold
            function() internal view returns (uint256) immutable _getPausableFlags;
            constructor(uint256 _nativeValueToCheckPauseState) {
                // Optimizes value check at runtime by reducing the stored immutable 
                // value by 1 so that greater than can be used instead of greater 
                // than or equal while allowing the deployment parameter to reflect 
                // the value at which the deployer wants to trigger pause checking.
                // Example: 
                //     Constructed with a value of 1000
                //     Immutable value stored is 999
                //     State checking enabled at 1000 units deposited because
                //     1000 > 999 evaluates true
                if (_nativeValueToCheckPauseState > 0) {
                    unchecked {
                        _nativeValueToCheckPauseState -= 1;
                    }
                    _requireNotPaused = _requireNotPausedWithCollateralCheck;
                    _requirePaused = _requirePausedWithCollateralCheck;
                    _getPausableFlags = _getPausableFlagsWithCollateralCheck;
                } else {
                    _requireNotPaused = _requireNotPausedWithoutCollateralCheck;
                    _requirePaused = _requirePausedWithoutCollateralCheck;
                    _getPausableFlags = _getPausableFlagsWithoutCollateralCheck;
                }
                nativeValueToCheckPauseState = _nativeValueToCheckPauseState;
            }
            /**
             * @dev  Modifier to make a function callable only when the specified flags are not paused
             * @dev  Throws when any of the flags specified are paused
             * 
             * @param _flags  The flags to check for pause state
             */
            modifier whenNotPaused(uint256 _flags) {
                _requireNotPaused(_flags);
                _;
            }
            /**
             * @dev  Modifier to make a function callable only when the specified flags are paused
             * @dev  Throws when any of the flags specified are not paused
             * 
             * @param _flags  The flags to check for pause state
             */
            modifier whenPaused(uint256 _flags) {
                _requirePaused(_flags);
                _;
            }
            /**
             * @dev  Modifier to make a function callable only by a permissioned account
             * @dev  Throws when the caller does not have permission
             */
            modifier onlyPausePermissionedCaller() {
                _requireCallerHasPausePermissions();
                _;
            }
            /**
             * @notice  Updates the pausable flags settings
             * 
             * @dev     Throws when the caller does not have permission
             * @dev     **NOTE:** Pausable flag settings will only take effect if contract balance exceeds 
             * @dev     `nativeValueToPause`
             * 
             * @dev     <h4>Postconditions:</h4>
             * @dev     1. address(this).balance increases by msg.value
             * @dev     2. `pausableFlags` is set to the new value
             * @dev     3. Emits a PausableFlagsUpdated event
             * 
             * @param _pausableFlags  The new pausable flags to set
             */
            function pause(uint256 _pausableFlags) external payable onlyPausePermissionedCaller {
                _setPausableFlags(_pausableFlags);
            }
            /**
             * @notice  Allows any account to supply funds for enabling the pausable checks
             * 
             * @dev     **NOTE:** The threshold check for pausable collateral does not pause
             * @dev     any functions unless the associated pausable flag is set.
             */
            function pausableDepositCollateral() external payable {
                // thank you for your contribution to safety
            }
            /**
             * @notice  Resets all pausable flags to unpaused and withdraws funds
             * 
             * @dev     Throws when the caller does not have permission
             * 
             * @dev     <h4>Postconditions:</h4>
             * @dev     1. `pausableFlags` is set to zero
             * @dev     2. Emits a PausableFlagsUpdated event
             * @dev     3. Transfers `withdrawAmount` of native funds to `withdrawTo` if non-zero
             * 
             * @param withdrawTo      The address to withdraw the collateral to
             * @param withdrawAmount  The amount of collateral to withdraw
             */
            function unpause(address withdrawTo, uint256 withdrawAmount) external onlyPausePermissionedCaller {
                _setPausableFlags(0);
                if (withdrawAmount > 0) {
                    (bool success, ) = withdrawTo.call{value: withdrawAmount}("");
                    if(!success) revert CollateralizedPausableFlags__WithdrawFailed();
                }
            }
            /**
             * @notice  Returns collateralized pausable configuration information
             * 
             * @return _nativeValueToCheckPauseState  The collateral required to enable pause state checking
             * @return _pausableFlags                 The current pausable flags set, only checked when collateral met
             */
            function pausableConfigurationSettings() external view returns(
                uint256 _nativeValueToCheckPauseState, 
                uint256 _pausableFlags
            ) {
                unchecked {
                    _nativeValueToCheckPauseState = nativeValueToCheckPauseState + 1;
                    _pausableFlags = pausableFlags;
                }
            }
            /**
             * @notice  Updates the `pausableFlags` variable and emits a PausableFlagsUpdated event
             * 
             * @param _pausableFlags  The new pausable flags to set
             */
            function _setPausableFlags(uint256 _pausableFlags) internal {
                uint256 previousFlags = pausableFlags;
                pausableFlags = _pausableFlags;
                emit PausableFlagsUpdated(previousFlags, _pausableFlags);
            }
            /**
             * @notice  Checks the current pause state of the supplied flags and reverts if any are paused
             * 
             * @dev     *Should* be called prior to any transfers of native funds out of the contract for efficiency
             * @dev     Throws when the native funds balance is greater than the value to enable pausing AND
             * @dev     one or more of the supplied `_flags` is paused.
             * 
             * @param _flags  The flags to check for pause state
             */
            function _requireNotPausedWithCollateralCheck(uint256 _flags) private view {
                if (_nativeBalanceSubMsgValue() > nativeValueToCheckPauseState) {
                    if (pausableFlags & _flags > 0) {
                        revert CollateralizedPausableFlags__Paused();
                    }
                }
            }
            /**
             * @notice  Checks the current pause state of the supplied flags and reverts if any are paused
             * 
             * @dev     Throws when one or more of the supplied `_flags` is paused.
             * 
             * @param _flags  The flags to check for pause state
             */
            function _requireNotPausedWithoutCollateralCheck(uint256 _flags) private view {
                if (pausableFlags & _flags > 0) {
                    revert CollateralizedPausableFlags__Paused();
                }
            }
            /**
             * @notice  Checks the current pause state of the supplied flags and reverts if none are paused
             * 
             * @dev     *Should* be called prior to any transfers of native funds out of the contract for efficiency
             * @dev     Throws when the native funds balance is not greater than the value to enable pausing OR
             * @dev     none of the supplied `_flags` are paused.
             * 
             * @param _flags  The flags to check for pause state
             */
            function _requirePausedWithCollateralCheck(uint256 _flags) private view {
                if (_nativeBalanceSubMsgValue() <= nativeValueToCheckPauseState) {
                    revert CollateralizedPausableFlags__NotPaused();
                } else if (pausableFlags & _flags == 0) {
                    revert CollateralizedPausableFlags__NotPaused();
                }
            }
            /**
             * @notice  Checks the current pause state of the supplied flags and reverts if none are paused
             * 
             * @dev     Throws when none of the supplied `_flags` are paused.
             * 
             * @param _flags  The flags to check for pause state
             */
            function _requirePausedWithoutCollateralCheck(uint256 _flags) private view {
                if (pausableFlags & _flags == 0) {
                    revert CollateralizedPausableFlags__NotPaused();
                }
            }
            /**
             * @notice  Returns the current state of the pausable flags
             * 
             * @dev     Will return zero if the native funds balance is not greater than the value to enable pausing
             * 
             * @return _pausableFlags  The current state of the pausable flags
             */
            function _getPausableFlagsWithCollateralCheck() private view returns(uint256 _pausableFlags) {
                if (_nativeBalanceSubMsgValue() > nativeValueToCheckPauseState) {
                    _pausableFlags = pausableFlags;
                }
            }
            /**
             * @notice  Returns the current state of the pausable flags
             * 
             * @return _pausableFlags  The current state of the pausable flags
             */
            function _getPausableFlagsWithoutCollateralCheck() private view returns(uint256 _pausableFlags) {
                _pausableFlags = pausableFlags;
            }
            /**
             * @notice  Returns the current contract balance minus the value sent with the call
             * 
             * @dev     This is expected to be the contract balance at the beginning of a function call
             * @dev     to efficiently determine whether a contract has the necessary collateral to enable
             * @dev     the pausable flags checking for contracts that hold native token funds.
             * @dev     This should **NOT** be used in any way to determine current balance for contract logic
             * @dev     other than its intended purpose for pause state checking activation.
             */
            function _nativeBalanceSubMsgValue() private view returns (uint256 _value) {
                unchecked {
                    _value = address(this).balance - msg.value;
                }
            }
            /**
             * @dev  To be implemented by an inheriting contract for authorization to `pause` and `unpause` 
             * @dev  functions as well as any functions in the inheriting contract that utilize the
             * @dev  `onlyPausePermissionedCaller` modifier.
             * 
             * @dev  Implementing contract function **MUST** throw when the caller is not permissioned
             */
            function _requireCallerHasPausePermissions() internal view virtual;
        }// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.4;
        /// @dev Constant bytes32 value of 0x000...000
        bytes32 constant ZERO_BYTES32 = bytes32(0);
        /// @dev Constant value of 0
        uint256 constant ZERO = 0;
        /// @dev Constant value of 1
        uint256 constant ONE = 1;
        /// @dev Constant value representing an open order in storage
        uint8 constant ORDER_STATE_OPEN = 0;
        /// @dev Constant value representing a filled order in storage
        uint8 constant ORDER_STATE_FILLED = 1;
        /// @dev Constant value representing a cancelled order in storage
        uint8 constant ORDER_STATE_CANCELLED = 2;
        /// @dev Constant value representing the ERC721 token type for signatures and transfer hooks
        uint256 constant TOKEN_TYPE_ERC721 = 721;
        /// @dev Constant value representing the ERC1155 token type for signatures and transfer hooks
        uint256 constant TOKEN_TYPE_ERC1155 = 1155;
        /// @dev Constant value representing the ERC20 token type for signatures and transfer hooks
        uint256 constant TOKEN_TYPE_ERC20 = 20;
        /// @dev Constant value to mask the upper bits of a signature that uses a packed `vs` value to extract `s`
        bytes32 constant UPPER_BIT_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
        /// @dev EIP-712 typehash used for validating signature based stored approvals
        bytes32 constant UPDATE_APPROVAL_TYPEHASH =
            keccak256("UpdateApprovalBySignature(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 approvalExpiration,uint256 sigDeadline,uint256 masterNonce)");
        /// @dev EIP-712 typehash used for validating a single use permit without additional data
        bytes32 constant SINGLE_USE_PERMIT_TYPEHASH =
            keccak256("PermitTransferFrom(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce)");
        /// @dev EIP-712 typehash used for validating a single use permit with additional data
        string constant SINGLE_USE_PERMIT_TRANSFER_ADVANCED_TYPEHASH_STUB =
            "PermitTransferFromWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce,";
        /// @dev EIP-712 typehash used for validating an order permit that updates storage as it fills
        string constant PERMIT_ORDER_ADVANCED_TYPEHASH_STUB =
            "PermitOrderWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 salt,address operator,uint256 expiration,uint256 masterNonce,";
        /// @dev Pausable flag for stored approval transfers of ERC721 assets
        uint256 constant PAUSABLE_APPROVAL_TRANSFER_FROM_ERC721 = 1 << 0;
        /// @dev Pausable flag for stored approval transfers of ERC1155 assets
        uint256 constant PAUSABLE_APPROVAL_TRANSFER_FROM_ERC1155 = 1 << 1;
        /// @dev Pausable flag for stored approval transfers of ERC20 assets
        uint256 constant PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20 = 1 << 2;
        /// @dev Pausable flag for single use permit transfers of ERC721 assets
        uint256 constant PAUSABLE_PERMITTED_TRANSFER_FROM_ERC721 = 1 << 3;
        /// @dev Pausable flag for single use permit transfers of ERC1155 assets
        uint256 constant PAUSABLE_PERMITTED_TRANSFER_FROM_ERC1155 = 1 << 4;
        /// @dev Pausable flag for single use permit transfers of ERC20 assets
        uint256 constant PAUSABLE_PERMITTED_TRANSFER_FROM_ERC20 = 1 << 5;
        /// @dev Pausable flag for order fill transfers of ERC1155 assets
        uint256 constant PAUSABLE_ORDER_TRANSFER_FROM_ERC1155 = 1 << 6;
        /// @dev Pausable flag for order fill transfers of ERC20 assets
        uint256 constant PAUSABLE_ORDER_TRANSFER_FROM_ERC20 = 1 << 7;// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.4;
        /// @dev Storage data struct for stored approvals and order approvals
        struct PackedApproval {
            // Only used for partial fill position 1155 transfers
            uint8 state;
            // Amount allowed
            uint200 amount;
            // Permission expiry
            uint48 expiration;
        }
        /// @dev Calldata data struct for order fill amounts
        struct OrderFillAmounts {
            uint256 orderStartAmount;
            uint256 requestedFillAmount;
            uint256 minimumFillAmount;
        }// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.4;
        /// @dev Thrown when a stored approval exceeds type(uint200).max
        error PermitC__AmountExceedsStorageMaximum();
        /// @dev Thrown when a transfer amount requested exceeds the permitted amount
        error PermitC__ApprovalTransferExceededPermittedAmount();
        /// @dev Thrown when a transfer is requested after the permit has expired
        error PermitC__ApprovalTransferPermitExpiredOrUnset();
        /// @dev Thrown when attempting to close an order by an account that is not the owner or operator
        error PermitC__CallerMustBeOwnerOrOperator();
        /// @dev Thrown when attempting to approve a token type that is not valid for PermitC
        error PermitC__InvalidTokenType();
        /// @dev Thrown when attempting to invalidate a nonce that has already been used
        error PermitC__NonceAlreadyUsedOrRevoked();
        /// @dev Thrown when attempting to restore a nonce that has not been used
        error PermitC__NonceNotUsedOrRevoked();
        /// @dev Thrown when attempting to fill an order that has already been filled or cancelled
        error PermitC__OrderIsEitherCancelledOrFilled();
        /// @dev Thrown when a transfer amount requested exceeds the permitted amount
        error PermitC__SignatureTransferExceededPermittedAmount();
        /// @dev Thrown when a transfer is requested after the permit has expired
        error PermitC__SignatureTransferExceededPermitExpired();
        /// @dev Thrown when attempting to use an advanced permit typehash that is not registered
        error PermitC__SignatureTransferPermitHashNotRegistered();
        /// @dev Thrown when a permit signature is invalid
        error PermitC__SignatureTransferInvalidSignature();
        /// @dev Thrown when the remaining fill amount is less than the requested minimum fill
        error PermitC__UnableToFillMinimumRequestedQuantity();// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.24;
        import "./Errors.sol";
        import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
        import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
        import {IERC1155} from "@openzeppelin/contracts/interfaces/IERC1155.sol";
        import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
        import {Ownable} from "./openzeppelin-optimized/Ownable.sol";
        import {EIP712} from "./openzeppelin-optimized/EIP712.sol";
        import {
            ZERO_BYTES32,
            ZERO, 
            ONE, 
            ORDER_STATE_OPEN,
            ORDER_STATE_FILLED,
            ORDER_STATE_CANCELLED,
            SINGLE_USE_PERMIT_TRANSFER_ADVANCED_TYPEHASH_STUB,
            PERMIT_ORDER_ADVANCED_TYPEHASH_STUB,
            UPPER_BIT_MASK,
            TOKEN_TYPE_ERC1155,
            TOKEN_TYPE_ERC20,
            TOKEN_TYPE_ERC721,
            PAUSABLE_APPROVAL_TRANSFER_FROM_ERC721,
            PAUSABLE_APPROVAL_TRANSFER_FROM_ERC1155,
            PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20,
            PAUSABLE_PERMITTED_TRANSFER_FROM_ERC721,
            PAUSABLE_PERMITTED_TRANSFER_FROM_ERC1155,
            PAUSABLE_PERMITTED_TRANSFER_FROM_ERC20,
            PAUSABLE_ORDER_TRANSFER_FROM_ERC1155,
            PAUSABLE_ORDER_TRANSFER_FROM_ERC20
        } from "./Constants.sol";
        import {PackedApproval, OrderFillAmounts} from "./DataTypes.sol";
        import {PermitHash} from './libraries/PermitHash.sol';
        import {IPermitC} from './interfaces/IPermitC.sol';
        import {CollateralizedPausableFlags} from './CollateralizedPausableFlags.sol';
        /*
                                                             @@@@@@@@@@@@@@             
                                                            @@@@@@@@@@@@@@@@@@(         
                                                           @@@@@@@@@@@@@@@@@@@@@        
                                                          @@@@@@@@@@@@@@@@@@@@@@@@      
                                                                   #@@@@@@@@@@@@@@      
                                                                       @@@@@@@@@@@@     
                                    @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                                   @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                                  @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                                 @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                                @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                                @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                               @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                            @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                            @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                           @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                          @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                         @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                        @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                        @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
                    @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                   .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
                   @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
                  @@@@@@@@@@@@@@@                                                       
                 @@@@@@@@@@@@@@@                                                        
                @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
               @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
               @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
         
        * @title PermitC
        * @custom:version 1.0.0
        * @author Limit Break, Inc.
        * @description Advanced approval management for ERC20, ERC721 and ERC1155 tokens
        *              allowing for single use permit transfers, time-bound approvals
        *              and order ID based transfers.
        */
        contract PermitC is Ownable, CollateralizedPausableFlags, EIP712, IPermitC {
            /**
             * @notice Map of approval details for the provided bytes32 hash to allow for multiple accessors
             *
             * @dev    keccak256(abi.encode(owner, tokenType, token, id, orderId, masterNonce)) => 
             * @dev        operator => (state, amount, expiration)
             * @dev    Utilized for stored approvals by an owner's direct call to `approve` and  
             * @dev    approvals by signature in `updateApprovalBySignature`. Both methods use a
             * @dev    bytes32(0) value for the `orderId`.
             */
            mapping(bytes32 => mapping(address => PackedApproval)) private _transferApprovals;
            /**
             * @notice Map of approval details for the provided bytes32 hash to allow for multiple accessors
             *
             * @dev    keccak256(abi.encode(owner, tokenType, token, id, orderId, masterNonce)) => 
             * @dev        operator => (state, amount, expiration)
             * @dev    Utilized for order approvals by `fillPermittedOrderERC20` and `fillPermittedOrderERC1155`
             * @dev    with the `orderId` provided by the sender.
             */
            mapping(bytes32 => mapping(address => PackedApproval)) private _orderApprovals;
            /**
             * @notice Map of registered additional data hashes for transfer permits.
             *
             * @dev    This is used to prevent someone from providing an invalid EIP712 envelope label
             * @dev    and tricking a user into signing a different message than they expect.
             */
            mapping(bytes32 => bool) private _registeredTransferHashes;
            /**
             * @notice Map of registered additional data hashes for order permits.
             *
             * @dev    This is used to prevent someone from providing an invalid EIP712 envelope label
             * @dev    and tricking a user into signing a different message than they expect.
             */
            mapping(bytes32 => bool) private _registeredOrderHashes;
            /// @dev Map of an address to a bitmap (slot => status)
            mapping(address => mapping(uint256 => uint256)) private _unorderedNonces;
            /**
             * @notice Master nonce used to invalidate all outstanding approvals for an owner
             *
             * @dev    owner => masterNonce
             * @dev    This is incremented when the owner calls lockdown()
             */
            mapping(address => uint256) private _masterNonces;
            constructor(
                string memory name,
                string memory version,
                address _defaultContractOwner,
                uint256 _nativeValueToCheckPauseState
            ) CollateralizedPausableFlags(_nativeValueToCheckPauseState) EIP712(name, version) {
                _transferOwnership(_defaultContractOwner);
            }
            /**
             * =================================================
             * ================= Modifiers =====================
             * =================================================
             */
            modifier onlyRegisteredTransferAdvancedTypeHash(bytes32 advancedPermitHash) {
                _requireTransferAdvancedPermitHashIsRegistered(advancedPermitHash);
                _;
            }
            modifier onlyRegisteredOrderAdvancedTypeHash(bytes32 advancedPermitHash) {
                _requireOrderAdvancedPermitHashIsRegistered(advancedPermitHash);
                _;
            }
            /**
             * =================================================
             * ============== Approval Transfers ===============
             * =================================================
             */
            /**
             * @notice Approve an operator to spend a specific token / ID combination
             * @notice This function is compatible with ERC20, ERC721 and ERC1155
             * @notice To give unlimited approval for ERC20 and ERC1155, set amount to type(uint200).max
             * @notice When approving an ERC721, you MUST set amount to `1`
             * @notice When approving an ERC20, you MUST set id to `0`
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Updates the approval for an operator to use an amount of a specific token / ID combination
             * @dev    2. If the expiration is 0, the approval is valid only in the context of the current block
             * @dev    3. If the expiration is not 0, the approval is valid until the expiration timestamp
             * @dev    4. If the provided amount is type(uint200).max, the approval is unlimited
             *
             * @param  tokenType  The type of token being approved - must be 20, 721 or 1155.
             * @param  token      The address of the token contract
             * @param  id         The token ID
             * @param  operator   The address of the operator
             * @param  amount     The amount of tokens to approve
             * @param  expiration The expiration timestamp of the approval
             */
            function approve(
                uint256 tokenType,
                address token, 
                uint256 id, 
                address operator, 
                uint200 amount, 
                uint48 expiration
            ) external {
                _requireValidTokenType(tokenType);
                _storeApproval(tokenType, token, id, amount, expiration, msg.sender, operator);
            }
            /**
             * @notice Use a signed permit to increase the allowance for a provided operator
             * @notice This function is compatible with ERC20, ERC721 and ERC1155
             * @notice To give unlimited approval for ERC20 and ERC1155, set amount to type(uint200).max
             * @notice When approving an ERC721, you MUST set amount to `1`
             * @notice When approving an ERC20, you MUST set id to `0`
             * @notice An `approvalExpiration` of zero is considered an atomic permit which will use the 
             * @notice current block time as the expiration time when storing the permit data.
             *
             * @dev    - Throws if the permit has expired
             * @dev    - Throws if the permit's nonce has already been used
             * @dev    - Throws if the permit signature is does not recover to the provided owner
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Updates the approval for an operator to use an amount of a specific token / ID combination
             * @dev    3. Sets the expiration of the approval to the expiration timestamp of the permit
             * @dev    4. If the provided amount is type(uint200).max, the approval is unlimited
             *
             * @param  tokenType            The type of token being approved - must be 20, 721 or 1155.
             * @param  token                Address of the token to approve
             * @param  id                   The token ID
             * @param  nonce                The nonce of the permit
             * @param  amount               The amount of tokens to approve
             * @param  operator             The address of the operator
             * @param  approvalExpiration   The expiration timestamp of the approval
             * @param  sigDeadline          The deadline timestamp for the permit signature
             * @param  owner                The owner of the tokens
             * @param  signedPermit         The permit signature, signed by the owner
             */
            function updateApprovalBySignature(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 nonce,
                uint200 amount,
                address operator,
                uint48 approvalExpiration,
                uint48 sigDeadline,
                address owner,
                bytes calldata signedPermit
            ) external {
                if (block.timestamp > sigDeadline) {
                    revert PermitC__ApprovalTransferPermitExpiredOrUnset();
                }
                _requireValidTokenType(tokenType);
                _checkAndInvalidateNonce(owner, nonce);
                _verifyPermitSignature(
                    _hashTypedDataV4(
                        PermitHash.hashOnChainApproval(
                            tokenType,
                            token,
                            id,
                            amount,
                            nonce,
                            operator,
                            approvalExpiration,
                            sigDeadline,
                            _masterNonces[owner]
                        )
                    ),
                    signedPermit, 
                    owner
                );
                // Expiration of zero is considered an atomic permit which is only valid in the 
                // current block.
                approvalExpiration = approvalExpiration == 0 ? uint48(block.timestamp) : approvalExpiration;
                _storeApproval(tokenType, token, id, amount, approvalExpiration, owner, operator);
            }
            /**
             * @notice Returns the amount of allowance an operator has and it's expiration for a specific token and id
             * @notice If the expiration on the allowance has expired, returns 0
             * @notice To retrieve allowance for ERC20, set id to `0`
             * 
             * @param  owner     The owner of the token
             * @param  operator  The operator of the token
             * @param  tokenType The type of token the allowance is for
             * @param  token     The address of the token contract
             * @param  id        The token ID
             *
             * @return allowedAmount The amount of allowance the operator has
             * @return expiration    The expiration timestamp of the allowance
             */
            function allowance(
                address owner, 
                address operator, 
                uint256 tokenType,
                address token, 
                uint256 id
            ) external view returns (uint256 allowedAmount, uint256 expiration) {
                return _allowance(_transferApprovals, owner, operator, tokenType, token, id, ZERO_BYTES32);
            }
            /**
             * =================================================
             * ================ Signed Transfers ===============
             * =================================================
             */
            /**
             * @notice Registers the combination of a provided string with the `SINGLE_USE_PERMIT_TRANSFER_ADVANCED_TYPEHASH_STUB` 
             * @notice and `PERMIT_ORDER_ADVANCED_TYPEHASH_STUB` to create valid additional data hashes
             *
             * @dev    This function prevents malicious actors from changing the label of the EIP712 hash
             * @dev    to a value that would fool an external user into signing a different message.
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. The provided string is combined with the `SINGLE_USE_PERMIT_TRANSFER_ADVANCED_TYPEHASH_STUB` string
             * @dev    2. The combined string is hashed using keccak256
             * @dev    3. The resulting hash is added to the `_registeredTransferHashes` mapping
             * @dev    4. The provided string is combined with the `PERMIT_ORDER_ADVANCED_TYPEHASH_STUB` string
             * @dev    5. The combined string is hashed using keccak256
             * @dev    6. The resulting hash is added to the `_registeredOrderHashes` mapping
             *
             * @param  additionalDataTypeString The string to register as a valid additional data hash
             */
            function registerAdditionalDataHash(string calldata additionalDataTypeString) external {
                _registeredTransferHashes[
                    keccak256(
                        bytes(
                            string.concat(
                                SINGLE_USE_PERMIT_TRANSFER_ADVANCED_TYPEHASH_STUB, 
                                additionalDataTypeString
                            )
                        )
                    )
                ] = true;
                _registeredOrderHashes[
                    keccak256(
                        bytes(
                            string.concat(
                                PERMIT_ORDER_ADVANCED_TYPEHASH_STUB, 
                                additionalDataTypeString
                            )
                        )
                    )
                ] = true;
            }
            /**
             * @notice Transfer an ERC721 token from the owner to the recipient using a permit signature.
             *
             * @dev    Be advised that the permitted amount for ERC721 is always inferred to be 1, so signed permitted amount
             * @dev    MUST always be set to 1.
             *
             * @dev    - Throws if the permit is expired
             * @dev    - Throws if the nonce has already been used
             * @dev    - Throws if the permit is not signed by the owner
             * @dev    - Throws if the requested amount exceeds the permitted amount
             * @dev    - Throws if the provided token address does not implement ERC721 transferFrom function
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token from the owner to the recipient
             * @dev    2. The nonce of the permit is marked as used
             * @dev    3. Performs any additional checks in the before and after hooks
             *
             * @param token         The address of the token
             * @param id            The ID of the token
             * @param nonce         The nonce of the permit
             * @param expiration    The expiration timestamp of the permit
             * @param owner         The owner of the token
             * @param to            The address to transfer the tokens to
             * @param signedPermit  The permit signature, signed by the owner
             *
             * @return isError      True if the transfer failed, false otherwise
             */
            function permitTransferFromERC721(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 expiration,
                address owner,
                address to,
                bytes calldata signedPermit
            ) external returns (bool isError) {
                _requireNotPaused(PAUSABLE_PERMITTED_TRANSFER_FROM_ERC721);
                _checkPermitApproval(TOKEN_TYPE_ERC721, token, id, ONE, nonce, expiration, owner, ONE, signedPermit);
                isError = _transferFromERC721(owner, to, token, id);
                if (isError) {
                    _restoreNonce(owner, nonce);
                }
            }
            /**
             * @notice Transfers an ERC721 token from the owner to the recipient using a permit signature
             * @notice This function includes additional data to verify on the signature, allowing
             * @notice protocols to extend the validation in one function call. NOTE: before calling this 
             * @notice function you MUST register the stub end of the additional data typestring using
             * @notice the `registerAdditionalDataHash` function.
             *
             * @dev    Be advised that the permitted amount for ERC721 is always inferred to be 1, so signed permitted amount
             * @dev    MUST always be set to 1.
             *
             * @dev    - Throws for any reason permitTransferFromERC721 would.
             * @dev    - Throws if the additional data does not match the signature
             * @dev    - Throws if the provided hash has not been registered as a valid additional data hash
             * @dev    - Throws if the provided hash does not match the provided additional data
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token from the owner to the recipient
             * @dev    2. Performs any additional checks in the before and after hooks
             * @dev    3. The nonce of the permit is marked as used
             * 
             * @param  token                    The address of the token
             * @param  id                       The ID of the token
             * @param  nonce                    The nonce of the permit
             * @param  expiration               The expiration timestamp of the permit
             * @param  owner                    The owner of the token
             * @param  to                       The address to transfer the tokens to
             * @param  additionalData           The additional data to verify on the signature
             * @param  advancedPermitHash       The hash of the additional data
             * @param  signedPermit             The permit signature, signed by the owner
             *
             * @return isError                  True if the transfer failed, false otherwise
             */
            function permitTransferFromWithAdditionalDataERC721(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 expiration,
                address owner,
                address to,
                bytes32 additionalData,
                bytes32 advancedPermitHash,
                bytes calldata signedPermit
           ) external onlyRegisteredTransferAdvancedTypeHash(advancedPermitHash) returns (bool isError) {
                _requireNotPaused(PAUSABLE_PERMITTED_TRANSFER_FROM_ERC721);
                _checkPermitApprovalWithAdditionalDataERC721(
                    token,
                    id,
                    ONE,
                    nonce,
                    expiration,
                    owner,
                    ONE,
                    signedPermit,
                    additionalData,
                    advancedPermitHash
                );
                isError = _transferFromERC721(owner, to, token, id);
                if (isError) {
                    _restoreNonce(owner, nonce);
                }
            }
            /**
             * @notice Transfer an ERC1155 token from the owner to the recipient using a permit signature
             *
             * @dev    - Throws if the permit is expired
             * @dev    - Throws if the nonce has already been used
             * @dev    - Throws if the permit is not signed by the owner
             * @dev    - Throws if the requested amount exceeds the permitted amount
             * @dev    - Throws if the provided token address does not implement ERC1155 safeTransferFrom function
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. The nonce of the permit is marked as used
             * @dev    3. Performs any additional checks in the before and after hooks
             *
             * @param token           The address of the token
             * @param id              The ID of the token
             * @param nonce           The nonce of the permit
             * @param permitAmount    The amount of tokens permitted by the owner
             * @param expiration      The expiration timestamp of the permit
             * @param owner           The owner of the token
             * @param to              The address to transfer the tokens to
             * @param transferAmount  The amount of tokens to transfer
             * @param signedPermit    The permit signature, signed by the owner
             *
             * @return isError        True if the transfer failed, false otherwise
             */
            function permitTransferFromERC1155(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes calldata signedPermit
            ) external returns (bool isError) {
                _requireNotPaused(PAUSABLE_PERMITTED_TRANSFER_FROM_ERC1155);
                _checkPermitApproval(TOKEN_TYPE_ERC1155, token, id, permitAmount, nonce, expiration, owner, transferAmount, signedPermit);
                isError = _transferFromERC1155(token, owner, to, id, transferAmount);
                if (isError) {
                    _restoreNonce(owner, nonce);
                }
            }
            /**
             * @notice Transfers a token from the owner to the recipient using a permit signature
             * @notice This function includes additional data to verify on the signature, allowing
             * @notice protocols to extend the validation in one function call. NOTE: before calling this 
             * @notice function you MUST register the stub end of the additional data typestring using
             * @notice the `registerAdditionalDataHash` function.
             *
             * @dev    - Throws for any reason permitTransferFrom would.
             * @dev    - Throws if the additional data does not match the signature
             * @dev    - Throws if the provided hash has not been registered as a valid additional data hash
             * @dev    - Throws if the provided hash does not match the provided additional data
             * @dev    - Throws if the provided hash has not been registered as a valid additional data hash
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Performs any additional checks in the before and after hooks
             * @dev    3. The nonce of the permit is marked as used
             *
             * @param  token                    The address of the token
             * @param  id                       The ID of the token
             * @param  nonce                    The nonce of the permit
             * @param  permitAmount             The amount of tokens permitted by the owner
             * @param  expiration               The expiration timestamp of the permit
             * @param  owner                    The owner of the token
             * @param  to                       The address to transfer the tokens to
             * @param  transferAmount           The amount of tokens to transfer
             * @param  additionalData           The additional data to verify on the signature
             * @param  advancedPermitHash       The hash of the additional data
             * @param  signedPermit             The permit signature, signed by the owner
             *
             * @return isError                  True if the transfer failed, false otherwise
             */
            function permitTransferFromWithAdditionalDataERC1155(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes32 additionalData,
                bytes32 advancedPermitHash,
                bytes calldata signedPermit
            ) external onlyRegisteredTransferAdvancedTypeHash(advancedPermitHash) returns (bool isError) {
                _requireNotPaused(PAUSABLE_PERMITTED_TRANSFER_FROM_ERC1155);
                _checkPermitApprovalWithAdditionalDataERC1155(
                    token,
                    id,
                    permitAmount,
                    nonce,
                    expiration,
                    owner,
                    transferAmount,
                    signedPermit,
                    additionalData,
                    advancedPermitHash
                );
                
                // copy id to top of stack to avoid stack too deep
                uint256 tmpId = id;
                isError = _transferFromERC1155(token, owner, to, tmpId, transferAmount);
                if (isError) {
                    _restoreNonce(owner, nonce);
                }
            }
            /**
             * @notice Transfer an ERC20 token from the owner to the recipient using a permit signature.
             *
             * @dev    Be advised that the token ID for ERC20 is always inferred to be 0, so signed token ID
             * @dev    MUST always be set to 0.
             *
             * @dev    - Throws if the permit is expired
             * @dev    - Throws if the nonce has already been used
             * @dev    - Throws if the permit is not signed by the owner
             * @dev    - Throws if the requested amount exceeds the permitted amount
             * @dev    - Throws if the provided token address does not implement ERC20 transferFrom function
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token in the requested amount from the owner to the recipient
             * @dev    2. The nonce of the permit is marked as used
             * @dev    3. Performs any additional checks in the before and after hooks
             *
             * @param token         The address of the token
             * @param nonce         The nonce of the permit
             * @param permitAmount  The amount of tokens permitted by the owner
             * @param expiration    The expiration timestamp of the permit
             * @param owner         The owner of the token
             * @param to            The address to transfer the tokens to
             * @param signedPermit  The permit signature, signed by the owner
             *
             * @return isError      True if the transfer failed, false otherwise
             */
            function permitTransferFromERC20(
                address token,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes calldata signedPermit
            ) external returns (bool isError) {
                _requireNotPaused(PAUSABLE_PERMITTED_TRANSFER_FROM_ERC20);
                _checkPermitApproval(TOKEN_TYPE_ERC20, token, ZERO, permitAmount, nonce, expiration, owner, transferAmount, signedPermit);
                isError = _transferFromERC20(token, owner, to, ZERO, transferAmount);
                if (isError) {
                    _restoreNonce(owner, nonce);
                }
            }
            /**
             * @notice Transfers an ERC20 token from the owner to the recipient using a permit signature
             * @notice This function includes additional data to verify on the signature, allowing
             * @notice protocols to extend the validation in one function call. NOTE: before calling this 
             * @notice function you MUST register the stub end of the additional data typestring using
             * @notice the `registerAdditionalDataHash` function.
             *
             * @dev    Be advised that the token ID for ERC20 is always inferred to be 0, so signed token ID
             * @dev    MUST always be set to 0.
             *
             * @dev    - Throws for any reason permitTransferFromERC20 would.
             * @dev    - Throws if the additional data does not match the signature
             * @dev    - Throws if the provided hash has not been registered as a valid additional data hash
             * @dev    - Throws if the provided hash does not match the provided additional data
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Performs any additional checks in the before and after hooks
             * @dev    3. The nonce of the permit is marked as used
             *
             * @param  token                    The address of the token
             * @param  nonce                    The nonce of the permit
             * @param  permitAmount             The amount of tokens permitted by the owner
             * @param  expiration               The expiration timestamp of the permit
             * @param  owner                    The owner of the token
             * @param  to                       The address to transfer the tokens to
             * @param  transferAmount           The amount of tokens to transfer
             * @param  additionalData           The additional data to verify on the signature
             * @param  advancedPermitHash       The hash of the additional data
             * @param  signedPermit             The permit signature, signed by the owner
             *
             * @return isError                  True if the transfer failed, false otherwise
             */
            function permitTransferFromWithAdditionalDataERC20(
                address token,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes32 additionalData,
                bytes32 advancedPermitHash,
                bytes calldata signedPermit
            ) external onlyRegisteredTransferAdvancedTypeHash(advancedPermitHash) returns (bool isError) {
                _requireNotPaused(PAUSABLE_PERMITTED_TRANSFER_FROM_ERC20);
                _checkPermitApprovalWithAdditionalDataERC20(
                    token,
                    ZERO,
                    permitAmount,
                    nonce,
                    expiration,
                    owner,
                    transferAmount,
                    signedPermit,
                    additionalData,
                    advancedPermitHash
                );
                isError = _transferFromERC20(token, owner, to, ZERO, transferAmount);
                if (isError) {
                    _restoreNonce(owner, nonce);
                }
            }
            /**
             * @notice Returns true if the provided hash has been registered as a valid additional data hash for transfers.
             *
             * @param  hash The hash to check
             *
             * @return isRegistered true if the hash is valid, false otherwise
             */
            function isRegisteredTransferAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered) {
                isRegistered = _registeredTransferHashes[hash];
            }
            /**
             * @notice Returns true if the provided hash has been registered as a valid additional data hash for orders.
             *
             * @param  hash The hash to check
             *
             * @return isRegistered true if the hash is valid, false otherwise
             */
            function isRegisteredOrderAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered) {
                isRegistered = _registeredOrderHashes[hash];
            }
            /**
             * =================================================
             * =============== Order Transfers =================
             * =================================================
             */
            /**
             * @notice Transfers an ERC1155 token from the owner to the recipient using a permit signature
             * @notice Order transfers are used to transfer a specific amount of a token from a specific order
             * @notice and allow for multiple uses of the same permit up to the allocated amount. NOTE: before calling this 
             * @notice function you MUST register the stub end of the additional data typestring using
             * @notice the `registerAdditionalDataHash` function.
             *
             * @dev    - Throws if the permit is expired
             * @dev    - Throws if the permit is not signed by the owner
             * @dev    - Throws if the requested amount + amount already filled exceeds the permitted amount
             * @dev    - Throws if the requested amount is less than the minimum fill amount
             * @dev    - Throws if the provided token address does not implement ERC1155 safeTransferFrom function
             * @dev    - Throws if the provided advanced permit hash has not been registered
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Updates the amount filled for the order ID
             * @dev    3. If completely filled, marks the order as filled
             * 
             * @param  signedPermit         The permit signature, signed by the owner
             * @param  orderFillAmounts     The amount of tokens to transfer
             * @param  token                The address of the token
             * @param  id                   The ID of the token
             * @param  owner                The owner of the token
             * @param  to                   The address to transfer the tokens to
             * @param  salt                 The salt of the permit
             * @param  expiration           The expiration timestamp of the permit
             * @param  orderId              The order ID
             * @param  advancedPermitHash   The hash of the additional data
             *
             * @return quantityFilled       The amount of tokens filled
             * @return isError              True if the transfer failed, false otherwise
             */
            function fillPermittedOrderERC1155(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                uint256 id,
                address owner,
                address to,
                uint256 salt,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) external onlyRegisteredOrderAdvancedTypeHash(advancedPermitHash) returns (uint256 quantityFilled, bool isError) {
                _requireNotPaused(PAUSABLE_ORDER_TRANSFER_FROM_ERC1155);
                PackedApproval storage orderStatus = _checkOrderTransferERC1155(
                    signedPermit,
                    orderFillAmounts,
                    token,
                    id,
                    owner,
                    salt,
                    expiration,
                    orderId,
                    advancedPermitHash
                );
                (
                    quantityFilled,
                    isError
                ) = _orderTransfer(
                        orderStatus,
                        orderFillAmounts,
                        token, 
                        id, 
                        owner, 
                        to, 
                        orderId,
                        _transferFromERC1155
                );
                if (isError) {
                    _restoreFillableItems(orderStatus, owner, orderId, quantityFilled, true);
                }
            }
            /**
             * @notice Transfers an ERC20 token from the owner to the recipient using a permit signature
             * @notice Order transfers are used to transfer a specific amount of a token from a specific order
             * @notice and allow for multiple uses of the same permit up to the allocated amount. NOTE: before calling this
             * @notice function you MUST register the stub end of the additional data typestring using
             * @notice the `registerAdditionalDataHash` function.
             *
             * @dev    - Throws if the permit is expired
             * @dev    - Throws if the permit is not signed by the owner
             * @dev    - Throws if the requested amount + amount already filled exceeds the permitted amount
             * @dev    - Throws if the requested amount is less than the minimum fill amount
             * @dev    - Throws if the provided token address does not implement ERC20 transferFrom function
             * @dev    - Throws if the provided advanced permit hash has not been registered
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Updates the amount filled for the order ID
             * @dev    3. If completely filled, marks the order as filled
             *
             * @param  signedPermit         The permit signature, signed by the owner
             * @param  orderFillAmounts     The amount of tokens to transfer
             * @param  token                The address of the token
             * @param  owner                The owner of the token
             * @param  to                   The address to transfer the tokens to
             * @param  salt                 The salt of the permit
             * @param  expiration           The expiration timestamp of the permit
             * @param  orderId              The order ID
             * @param  advancedPermitHash   The hash of the additional data
             *
             * @return quantityFilled       The amount of tokens filled
             * @return isError              True if the transfer failed, false otherwise
             */
            function fillPermittedOrderERC20(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                address owner,
                address to,
                uint256 salt,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) external onlyRegisteredOrderAdvancedTypeHash(advancedPermitHash) returns (uint256 quantityFilled, bool isError) {
                _requireNotPaused(PAUSABLE_ORDER_TRANSFER_FROM_ERC20);
                PackedApproval storage orderStatus = _checkOrderTransferERC20(
                    signedPermit,
                    orderFillAmounts,
                    token,
                    ZERO,
                    owner,
                    salt,
                    expiration,
                    orderId,
                    advancedPermitHash
                );
                (
                    quantityFilled,
                    isError
                ) = _orderTransfer(
                        orderStatus,
                        orderFillAmounts,
                        token, 
                        ZERO, 
                        owner, 
                        to, 
                        orderId,
                        _transferFromERC20
                );
                if (isError) {
                    _restoreFillableItems(orderStatus, owner, orderId, quantityFilled, true);
                }
            }
            /**
             * @notice Closes an outstanding order to prevent further execution of transfers.
             *
             * @dev    - Throws if the order is not in the open state
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Marks the order as cancelled
             * @dev    2. Sets the order amount to 0
             * @dev    3. Sets the order expiration to 0
             * @dev    4. Emits a OrderClosed event
             *
             * @param  owner      The owner of the token
             * @param  operator   The operator allowed to transfer the token
             * @param  tokenType  The type of token the order is for - must be 20, 721 or 1155.
             * @param  token      The address of the token contract
             * @param  id         The token ID
             * @param  orderId    The order ID
             */
            function closePermittedOrder(
                address owner,
                address operator,
                uint256 tokenType,
                address token,
                uint256 id,
                bytes32 orderId
            ) external {
                if(!(msg.sender == owner || msg.sender == operator)) {
                    revert PermitC__CallerMustBeOwnerOrOperator();
                }
                _requireValidTokenType(tokenType);
                PackedApproval storage orderStatus = _getPackedApprovalPtr(_orderApprovals, owner, tokenType, token, id, orderId, operator);
            
                if (orderStatus.state == ORDER_STATE_OPEN) {
                    orderStatus.state = ORDER_STATE_CANCELLED;
                    orderStatus.amount = 0;
                    orderStatus.expiration = 0;
                    emit OrderClosed(orderId, owner, operator, true);
                } else {
                    revert PermitC__OrderIsEitherCancelledOrFilled();
                }
            }
            /**
             * @notice Returns the amount of allowance an operator has for a specific token and id
             * @notice If the expiration on the allowance has expired, returns 0
             *
             * @dev    Overload of the on chain allowance function for approvals with a specified order ID
             * 
             * @param  owner    The owner of the token
             * @param  operator The operator of the token
             * @param  token    The address of the token contract
             * @param  id       The token ID
             *
             * @return allowedAmount The amount of allowance the operator has
             */
            function allowance(
                address owner, 
                address operator, 
                uint256 tokenType,
                address token, 
                uint256 id, 
                bytes32 orderId
            ) external view returns (uint256 allowedAmount, uint256 expiration) {
                return _allowance(_orderApprovals, owner, operator, tokenType, token, id, orderId);
            }
            /**
             * =================================================
             * ================ Nonce Management ===============
             * =================================================
             */
            /**
             * @notice Invalidates the provided nonce
             *
             * @dev    - Throws if the provided nonce has already been used
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Sets the provided nonce as used for the sender
             *
             * @param  nonce Nonce to invalidate
             */
            function invalidateUnorderedNonce(uint256 nonce) external {
                _checkAndInvalidateNonce(msg.sender, nonce);
            }
            /**
             * @notice Returns if the provided nonce has been used
             *
             * @param  owner The owner of the token
             * @param  nonce The nonce to check
             *
             * @return isValid true if the nonce is valid, false otherwise
             */
            function isValidUnorderedNonce(address owner, uint256 nonce) external view returns (bool isValid) {
                isValid = ((_unorderedNonces[owner][uint248(nonce >> 8)] >> uint8(nonce)) & ONE) == ZERO;
            }
            /**
             * @notice Revokes all outstanding approvals for the sender
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Increments the master nonce for the sender
             * @dev    2. All outstanding approvals for the sender are invalidated
             */
            function lockdown() external {
                unchecked {
                    _masterNonces[msg.sender]++;
                }
                emit Lockdown(msg.sender);
            }
            /**
             * @notice Returns the master nonce for the provided owner address
             *
             * @param  owner The owner address
             *
             * @return The master nonce
             */
            function masterNonce(address owner) external view returns (uint256) {
                return _masterNonces[owner];
            }
            /**
             * =================================================
             * ============== Transfer Functions ===============
             * =================================================
             */
            /**
             * @notice Transfer an ERC721 token from the owner to the recipient using on chain approvals
             *
             * @dev    Public transfer function overload for approval transfers
             * @dev    - Throws if the provided token address does not implement ERC721 transferFrom function
             * @dev    - Throws if the requested amount exceeds the approved amount
             * @dev    - Throws if the approval is expired
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Decrements the approval amount by the requested amount
             * @dev    3. Performs any additional checks in the before and after hooks
             *
             * @param  owner    The owner of the token
             * @param  to       The recipient of the token
             * @param  token    The address of the token
             * @param  id       The id of the token
             *
             * @return isError  True if the transfer failed, false otherwise
             */
            function transferFromERC721(
                address owner,
                address to,
                address token,
                uint256 id
            ) external returns (bool isError) {
                _requireNotPaused(PAUSABLE_APPROVAL_TRANSFER_FROM_ERC721);
                PackedApproval storage approval = _checkAndUpdateApproval(owner, TOKEN_TYPE_ERC721, token, id, ONE, true);
                isError = _transferFromERC721(owner, to, token, id);
                if (isError) {
                    _restoreFillableItems(approval, owner, ZERO_BYTES32, ONE, false);
                }
            }
            /**
             * @notice Transfer an ERC1155 token from the owner to the recipient using on chain approvals
             *
             * @dev    Public transfer function overload for approval transfers
             * @dev    - Throws if the provided token address does not implement ERC1155 safeTransferFrom function
             * @dev    - Throws if the requested amount exceeds the approved amount
             * @dev    - Throws if the approval is expired
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Decrements the approval amount by the requested amount
             * @dev    3. Performs any additional checks in the before and after hooks
             *
             * @param  owner     The owner of the token
             * @param  to       The recipient of the token
             * @param  amount   The amount of the token to transfer
             * @param  token    The address of the token
             * @param  id       The id of the token
             *
             * @return isError  True if the transfer failed, false otherwise
             */
            function transferFromERC1155(
                address owner,
                address to,
                address token,
                uint256 id,
                uint256 amount
            ) external returns (bool isError) {
                _requireNotPaused(PAUSABLE_APPROVAL_TRANSFER_FROM_ERC1155);
                PackedApproval storage approval = _checkAndUpdateApproval(owner, TOKEN_TYPE_ERC1155, token, id, amount, false);
                isError = _transferFromERC1155(token, owner, to, id, amount);
                if (isError) {
                    _restoreFillableItems(approval, owner, ZERO_BYTES32, amount, false);
                }
            }
            /**
             * @notice Transfer an ERC20 token from the owner to the recipient using on chain approvals
             *
             * @dev    Public transfer function overload for approval transfers
             * @dev    - Throws if the provided token address does not implement ERC20 transferFrom function
             * @dev    - Throws if the requested amount exceeds the approved amount
             * @dev    - Throws if the approval is expired
             * @dev    - Returns `false` if the transfer fails
             *
             * @dev    <h4>Postconditions:</h4>
             * @dev    1. Transfers the token (in the requested amount) from the owner to the recipient
             * @dev    2. Decrements the approval amount by the requested amount
             * @dev    3. Performs any additional checks in the before and after hooks
             *
             * @param  owner     The owner of the token
             * @param  to       The recipient of the token
             * @param  amount   The amount of the token to transfer
             * @param  token    The address of the token
             *
             * @return isError  True if the transfer failed, false otherwise
             */
            function transferFromERC20(
                address owner,
                address to,
                address token,
                uint256 amount
            ) external returns (bool isError) {
                _requireNotPaused(PAUSABLE_APPROVAL_TRANSFER_FROM_ERC20);
                PackedApproval storage approval = _checkAndUpdateApproval(owner, TOKEN_TYPE_ERC20, token, ZERO, amount, false);
                isError = _transferFromERC20(token, owner, to, ZERO, amount);
                if (isError) {
                    _restoreFillableItems(approval, owner, ZERO_BYTES32, amount, false);
                }
            }
            /**
             * @notice  Performs a transfer of an ERC721 token.
             * 
             * @dev     Will **NOT** attempt transfer if `_beforeTransferFrom` hook returns false.
             * @dev     Will **NOT** revert if the transfer is unsucessful.
             * @dev     Invokers **MUST** check `isError` return value to determine success.
             * 
             * @param owner  The owner of the token being transferred
             * @param to     The address to transfer the token to
             * @param token  The token address of the token being transferred
             * @param id     The token id being transferred
             * 
             * @return isError True if the token was not transferred, false if token was transferred
             */
            function _transferFromERC721(
                address owner,
                address to,
                address token,
                uint256 id
            ) private returns (bool isError) {
                isError = _beforeTransferFrom(TOKEN_TYPE_ERC721, token, owner, to, id, ONE);
                if (!isError) {
                    try IERC721(token).transferFrom(owner, to, id) { } 
                    catch {
                        isError = true;
                    }
                }
            }
            /**
             * @notice  Performs a transfer of an ERC1155 token.
             * 
             * @dev     Will **NOT** attempt transfer if `_beforeTransferFrom` hook returns false.
             * @dev     Will **NOT** revert if the transfer is unsucessful.
             * @dev     Invokers **MUST** check `isError` return value to determine success.
             * 
             * @param token  The token address of the token being transferred
             * @param owner  The owner of the token being transferred
             * @param to     The address to transfer the token to
             * @param id     The token id being transferred
             * @param amount The quantity of token id to transfer
             * 
             * @return isError True if the token was not transferred, false if token was transferred
             */
            function _transferFromERC1155(
                address token,
                address owner,
                address to,
                uint256 id,
                uint256 amount
            ) private returns (bool isError) {
                isError = _beforeTransferFrom(TOKEN_TYPE_ERC1155, token, owner, to, id, amount);
                if (!isError) {
                    try IERC1155(token).safeTransferFrom(owner, to, id, amount, "") { } catch {
                        isError = true;
                    }
                }
            }
            /**
             * @notice  Performs a transfer of an ERC20 token.
             * 
             * @dev     Will **NOT** attempt transfer if `_beforeTransferFrom` hook returns false.
             * @dev     Will **NOT** revert if the transfer is unsucessful.
             * @dev     Invokers **MUST** check `isError` return value to determine success.
             * 
             * @param token  The token address of the token being transferred
             * @param owner  The owner of the token being transferred
             * @param to     The address to transfer the token to
             * @param amount The quantity of token id to transfer
             * 
             * @return isError True if the token was not transferred, false if token was transferred
             */
            function _transferFromERC20(
                address token,
                address owner,
                address to,
                uint256 /*id*/,
                uint256 amount
              ) private returns (bool isError) {
                isError = _beforeTransferFrom(TOKEN_TYPE_ERC20, token, owner, to, ZERO, amount);
                if (!isError) {
                    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, owner, to, amount));
                    if (!success) {
                        isError = true;
                    } else if (data.length > 0) {
                        isError = !abi.decode(data, (bool));
                    }
                }
            }
            /**
             * =================================================
             * ============ Signature Verification =============
             * =================================================
             */
            /**
             * @notice Returns the domain separator used in the permit signature
             *
             * @return domainSeparator The domain separator
             */
            function domainSeparatorV4() external view returns (bytes32 domainSeparator) {
                domainSeparator = _domainSeparatorV4();
            }
            /**
             * @notice  Verifies a permit signature based on the bytes length of the signature provided.
             * 
             * @dev     Throws when -
             * @dev         The bytes signature length is 64 or 65 bytes AND
             * @dev         The ECDSA recovered signer is not the owner AND
             * @dev         The owner's code length is zero OR the owner does not return a valid EIP-1271 response
             * @dev 
             * @dev         OR
             * @dev
             * @dev         The bytes signature length is not 64 or 65 bytes AND
             * @dev         The owner's code length is zero OR the owner does not return a valid EIP-1271 response
             */
            function _verifyPermitSignature(bytes32 digest, bytes calldata signature, address owner) private view {
                if (signature.length == 65) {
                    bytes32 r;
                    bytes32 s;
                    uint8 v;
                    // Divide the signature in r, s and v variables
                    /// @solidity memory-safe-assembly
                    assembly {
                        r := calldataload(signature.offset)
                        s := calldataload(add(signature.offset, 32))
                        v := byte(0, calldataload(add(signature.offset, 64)))
                    }
                    (bool isError, address signer) = _ecdsaRecover(digest, v, r, s);
                    if (owner != signer || isError) {
                        _verifyEIP1271Signature(owner, digest, signature);
                    }
                } else if (signature.length == 64) {
                    bytes32 r;
                    bytes32 vs;
                    // Divide the signature in r and vs variables
                    /// @solidity memory-safe-assembly
                    assembly {
                        r := calldataload(signature.offset)
                        vs := calldataload(add(signature.offset, 32))
                    }
                    (bool isError, address signer) = _ecdsaRecover(digest, r, vs);
                    if (owner != signer || isError) {
                        _verifyEIP1271Signature(owner, digest, signature);
                    }
                } else {
                    _verifyEIP1271Signature(owner, digest, signature);
                }
            }
            /**
             * @notice Verifies an EIP-1271 signature.
             * 
             * @dev    Throws when `signer` code length is zero OR the EIP-1271 call does not
             * @dev    return the correct magic value.
             * 
             * @param signer     The signer address to verify a signature with
             * @param hash       The hash digest to verify with the signer
             * @param signature  The signature to verify
             */
            function _verifyEIP1271Signature(address signer, bytes32 hash, bytes calldata signature) private view {
                if(signer.code.length == 0) {
                    revert PermitC__SignatureTransferInvalidSignature();
                }
                if (!_safeIsValidSignature(signer, hash, signature)) {
                    revert PermitC__SignatureTransferInvalidSignature();
                }
            }
            /**
             * @notice  Overload of the `_ecdsaRecover` function to unpack the `v` and `s` values
             * 
             * @param digest    The hash digest that was signed
             * @param r         The `r` value of the signature
             * @param vs        The packed `v` and `s` values of the signature
             * 
             * @return isError  True if the ECDSA function is provided invalid inputs
             * @return signer   The recovered address from ECDSA
             */
            function _ecdsaRecover(bytes32 digest, bytes32 r, bytes32 vs) private pure returns (bool isError, address signer) {
                unchecked {
                    bytes32 s = vs & UPPER_BIT_MASK;
                    uint8 v = uint8(uint256(vs >> 255)) + 27;
                    (isError, signer) = _ecdsaRecover(digest, v, r, s);
                }
            }
            /**
             * @notice  Recovers the signer address using ECDSA
             * 
             * @dev     Does **NOT** revert if invalid input values are provided or `signer` is recovered as address(0)
             * @dev     Returns an `isError` value in those conditions that is handled upstream
             * 
             * @param digest    The hash digest that was signed
             * @param v         The `v` value of the signature
             * @param r         The `r` value of the signature
             * @param s         The `s` value of the signature
             * 
             * @return isError  True if the ECDSA function is provided invalid inputs
             * @return signer   The recovered address from ECDSA
             */
            function _ecdsaRecover(bytes32 digest, uint8 v, bytes32 r, bytes32 s) private pure returns (bool isError, address signer) {
                if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                    // Invalid signature `s` value - return isError = true and signer = address(0) to check EIP-1271
                    return (true, address(0));
                }
                signer = ecrecover(digest, v, r, s);
                isError = (signer == address(0));
            }
            /**
             * @notice A gas efficient, and fallback-safe way to call the isValidSignature function for EIP-1271.
             *
             * @param signer     The EIP-1271 signer to call to check for a valid signature.
             * @param hash       The hash digest to verify with the EIP-1271 signer.
             * @param signature  The supplied signature to verify.
             * 
             * @return isValid   True if the EIP-1271 signer returns the EIP-1271 magic value.
             */
            function _safeIsValidSignature(
                address signer,
                bytes32 hash,
                bytes calldata signature
            ) private view returns(bool isValid) {
                assembly {
                    function _callIsValidSignature(_signer, _hash, _signatureOffset, _signatureLength) -> _isValid {
                        let ptr := mload(0x40)
                        // store isValidSignature(bytes32,bytes) selector
                        mstore(ptr, hex"1626ba7e")
                        // store bytes32 hash value in abi encoded location
                        mstore(add(ptr, 0x04), _hash)
                        // store abi encoded location of the bytes signature data
                        mstore(add(ptr, 0x24), 0x40)
                        // store bytes signature length
                        mstore(add(ptr, 0x44), _signatureLength)
                        // copy calldata bytes signature to memory
                        calldatacopy(add(ptr, 0x64), _signatureOffset, _signatureLength)
                        // calculate data length based on abi encoded data with rounded up signature length
                        let dataLength := add(0x64, and(add(_signatureLength, 0x1F), not(0x1F)))
                        // update free memory pointer
                        mstore(0x40, add(ptr, dataLength))
                        // static call _signer with abi encoded data
                        // skip return data check if call failed or return data size is not at least 32 bytes
                        if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _signer, ptr, dataLength, 0x00, 0x20)) {
                            // check if return data is equal to isValidSignature magic value
                            _isValid := eq(mload(0x00), hex"1626ba7e")
                            leave
                        }
                    }
                    isValid := _callIsValidSignature(signer, hash, signature.offset, signature.length)
                }
            }
            /**
             * =================================================
             * ===================== Hooks =====================
             * =================================================
             */
            /**
             * @dev    This function is empty by default. Override it to add additional logic after the approval transfer.
             * @dev    The function returns a boolean value instead of reverting to indicate if there is an error for more granular control in inheriting protocols.
             */
            function _beforeTransferFrom(uint256 tokenType, address token, address owner, address to, uint256 id, uint256 amount) internal virtual returns (bool isError) {}
            /**
             * =================================================
             * ==================== Internal ===================
             * =================================================
             */
            /**
             * @notice Checks if an advanced permit typehash has been registered with PermitC
             * 
             * @dev    Throws when the typehash has not been registered
             * 
             * @param advancedPermitHash  The permit typehash to check
             */
            function _requireTransferAdvancedPermitHashIsRegistered(bytes32 advancedPermitHash) private view {
                if (!_registeredTransferHashes[advancedPermitHash]) {
                    revert PermitC__SignatureTransferPermitHashNotRegistered();
                }
            }
            /**
             * @notice Checks if an advanced permit typehash has been registered with PermitC
             * 
             * @dev    Throws when the typehash has not been registered
             * 
             * @param advancedPermitHash  The permit typehash to check
             */
            function _requireOrderAdvancedPermitHashIsRegistered(bytes32 advancedPermitHash) private view {
                if (!_registeredOrderHashes[advancedPermitHash]) {
                    revert PermitC__SignatureTransferPermitHashNotRegistered();
                }
            }
            /**
             * @notice  Invalidates an account nonce if it has not been previously used
             * 
             * @dev     Throws when the nonce was previously used
             * 
             * @param account  The account to invalidate the nonce of
             * @param nonce    The nonce to invalidate
             */
            function _checkAndInvalidateNonce(address account, uint256 nonce) private {
                unchecked {
                    if (uint256(_unorderedNonces[account][uint248(nonce >> 8)] ^= (ONE << uint8(nonce))) & 
                        (ONE << uint8(nonce)) == ZERO) {
                        revert PermitC__NonceAlreadyUsedOrRevoked();
                    }
                }
            }
            /**
             * @notice Checks an approval to ensure it is sufficient for the `amount` to send
             * 
             * @dev    Throws when the approval is expired
             * @dev    Throws when the approved amount is insufficient
             * 
             * @param owner            The owner of the token
             * @param tokenType        The type of token
             * @param token            The address of the token
             * @param id               The id of the token
             * @param amount           The amount to deduct from the approval
             * @param zeroOutApproval  True if the approval should be set to zero
             * 
             * @return approval  Storage pointer for the approval data
             */
            function _checkAndUpdateApproval(
                address owner,
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 amount,
                bool zeroOutApproval
            ) private returns (PackedApproval storage approval) {
                approval = _getPackedApprovalPtr(_transferApprovals, owner, tokenType, token, id, ZERO_BYTES32, msg.sender);
                
                if (approval.expiration < block.timestamp) {
                    revert PermitC__ApprovalTransferPermitExpiredOrUnset();
                }
                if (approval.amount < amount) {
                    revert PermitC__ApprovalTransferExceededPermittedAmount();
                }
                if(zeroOutApproval) {
                    approval.amount = 0;
                } else if (approval.amount < type(uint200).max) {
                    unchecked {
                        approval.amount -= uint200(amount);
                    }
                }
            }
            /**
             * @notice  Gets the storage pointer for an approval
             * 
             * @param _approvals  The mapping to retrieve the approval from
             * @param account     The account the approval is from
             * @param tokenType   The type of token the approval is for
             * @param token       The address of the token
             * @param id          The id of the token
             * @param orderId     The order id for the approval
             * @param operator    The operator for the approval
             * 
             * @return approval  Storage pointer for the approval data
             */
            function _getPackedApprovalPtr(
                mapping(bytes32 => mapping(address => PackedApproval)) storage _approvals,
                address account, 
                uint256 tokenType,
                address token, 
                uint256 id,
                bytes32 orderId,
                address operator
            ) private view returns (PackedApproval storage approval) {
                approval = _approvals[_getPackedApprovalKey(account, tokenType, token, id, orderId)][operator];
            }
            /**
             * @notice  Gets the storage key for the mapping for a specific approval
             * 
             * @param owner      The owner of the token
             * @param tokenType  The type of token
             * @param token      The address of the token
             * @param id         The id of the token
             * @param orderId    The order id of the approval
             * 
             * @return key  The key value to use to access the approval in the mapping
             */
            function _getPackedApprovalKey(address owner, uint256 tokenType, address token, uint256 id, bytes32 orderId) private view returns (bytes32 key) {
                key = keccak256(abi.encode(owner, tokenType, token, id, orderId, _masterNonces[owner]));
            }
            /**
             * @notice Checks the permit approval for a single use permit without additional data
             * 
             * @dev    Throws when the `nonce` has already been consumed
             * @dev    Throws when the permit amount is less than the transfer amount
             * @dev    Throws when the permit is expired
             * @dev    Throws when the signature is invalid
             * 
             * @param tokenType       The type of token
             * @param token           The address of the token
             * @param id              The id of the token
             * @param permitAmount    The amount authorized by the owner signature
             * @param nonce           The nonce of the permit
             * @param expiration      The time the permit expires
             * @param owner           The owner of the token
             * @param transferAmount  The amount of tokens requested to transfer
             * @param signedPermit    The signature for the permit
             */
            function _checkPermitApproval(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 permitAmount,
                uint256 nonce,
                uint256 expiration,
                address owner,
                uint256 transferAmount,
                bytes calldata signedPermit
            ) private {
                bytes32 digest = _hashTypedDataV4(
                    PermitHash.hashSingleUsePermit(
                        tokenType,
                        token,
                        id,
                        permitAmount,
                        nonce,
                        expiration,
                        _masterNonces[owner]
                    )
                );
                _checkPermitData(
                    nonce,
                    expiration,
                    transferAmount,
                    permitAmount,
                    owner,
                    digest,
                    signedPermit
                );
            }
            /**
             * @notice  Overload of `_checkPermitApprovalWithAdditionalData` to supply TOKEN_TYPE_ERC1155
             * 
             * @dev     Prevents stack too deep in `permitTransferFromWithAdditionalDataERC1155`
             * @dev     Throws when the `nonce` has already been consumed
             * @dev     Throws when the permit amount is less than the transfer amount
             * @dev     Throws when the permit is expired
             * @dev     Throws when the signature is invalid
             * 
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param permitAmount        The amount authorized by the owner signature
             * @param nonce               The nonce of the permit
             * @param expiration          The time the permit expires
             * @param owner               The owner of the token
             * @param transferAmount      The amount of tokens requested to transfer
             * @param signedPermit        The signature for the permit
             * @param additionalData      The additional data to validate with the permit signature
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             */
            function _checkPermitApprovalWithAdditionalDataERC1155(
                address token,
                uint256 id,
                uint256 permitAmount,
                uint256 nonce,
                uint256 expiration,
                address owner,
                uint256 transferAmount,
                bytes calldata signedPermit,
                bytes32 additionalData,
                bytes32 advancedPermitHash
            ) private {
                _checkPermitApprovalWithAdditionalData(
                    TOKEN_TYPE_ERC1155,
                    token,
                    id,
                    permitAmount,
                    nonce,
                    expiration,
                    owner,
                    transferAmount,
                    signedPermit,
                    additionalData,
                    advancedPermitHash
                );
            }
            /**
             * @notice  Overload of `_checkPermitApprovalWithAdditionalData` to supply TOKEN_TYPE_ERC20
             * 
             * @dev     Prevents stack too deep in `permitTransferFromWithAdditionalDataERC220`
             * @dev     Throws when the `nonce` has already been consumed
             * @dev     Throws when the permit amount is less than the transfer amount
             * @dev     Throws when the permit is expired
             * @dev     Throws when the signature is invalid
             * 
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param permitAmount        The amount authorized by the owner signature
             * @param nonce               The nonce of the permit
             * @param expiration          The time the permit expires
             * @param owner               The owner of the token
             * @param transferAmount      The amount of tokens requested to transfer
             * @param signedPermit        The signature for the permit
             * @param additionalData      The additional data to validate with the permit signature
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             */
            function _checkPermitApprovalWithAdditionalDataERC20(
                address token,
                uint256 id,
                uint256 permitAmount,
                uint256 nonce,
                uint256 expiration,
                address owner,
                uint256 transferAmount,
                bytes calldata signedPermit,
                bytes32 additionalData,
                bytes32 advancedPermitHash
            ) private {
                _checkPermitApprovalWithAdditionalData(
                    TOKEN_TYPE_ERC20,
                    token,
                    id,
                    permitAmount,
                    nonce,
                    expiration,
                    owner,
                    transferAmount,
                    signedPermit,
                    additionalData,
                    advancedPermitHash
                );
            }
            /**
             * @notice  Overload of `_checkPermitApprovalWithAdditionalData` to supply TOKEN_TYPE_ERC721
             * 
             * @dev     Prevents stack too deep in `permitTransferFromWithAdditionalDataERC721`
             * @dev     Throws when the `nonce` has already been consumed
             * @dev     Throws when the permit amount is less than the transfer amount
             * @dev     Throws when the permit is expired
             * @dev     Throws when the signature is invalid
             * 
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param permitAmount        The amount authorized by the owner signature
             * @param nonce               The nonce of the permit
             * @param expiration          The time the permit expires
             * @param owner               The owner of the token
             * @param transferAmount      The amount of tokens requested to transfer
             * @param signedPermit        The signature for the permit
             * @param additionalData      The additional data to validate with the permit signature
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             */
            function _checkPermitApprovalWithAdditionalDataERC721(
                address token,
                uint256 id,
                uint256 permitAmount,
                uint256 nonce,
                uint256 expiration,
                address owner,
                uint256 transferAmount,
                bytes calldata signedPermit,
                bytes32 additionalData,
                bytes32 advancedPermitHash
            ) private {
                _checkPermitApprovalWithAdditionalData(
                    TOKEN_TYPE_ERC721,
                    token,
                    id,
                    permitAmount,
                    nonce,
                    expiration,
                    owner,
                    transferAmount,
                    signedPermit,
                    additionalData,
                    advancedPermitHash
                );
            }
            /**
             * @notice Checks the permit approval for a single use permit with additional data
             * 
             * @dev    Throws when the `nonce` has already been consumed
             * @dev    Throws when the permit amount is less than the transfer amount
             * @dev    Throws when the permit is expired
             * @dev    Throws when the signature is invalid
             * 
             * @param tokenType           The type of token
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param permitAmount        The amount authorized by the owner signature
             * @param nonce               The nonce of the permit
             * @param expiration          The time the permit expires
             * @param owner               The owner of the token
             * @param transferAmount      The amount of tokens requested to transfer
             * @param signedPermit        The signature for the permit
             * @param additionalData      The additional data to validate with the permit signature
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             */
            function _checkPermitApprovalWithAdditionalData(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 permitAmount,
                uint256 nonce,
                uint256 expiration,
                address owner,
                uint256 transferAmount,
                bytes calldata signedPermit,
                bytes32 additionalData,
                bytes32 advancedPermitHash
            ) private {
                bytes32 digest = _getAdvancedTypedDataV4PermitHash(
                    tokenType,
                    token, 
                    id, 
                    permitAmount, 
                    owner,
                    nonce, 
                    expiration, 
                    additionalData, 
                    advancedPermitHash
                );        
                _checkPermitData(
                    nonce,
                    expiration,
                    transferAmount,
                    permitAmount,
                    owner,
                    digest,
                    signedPermit
                );
            }
            /**
             * @notice  Checks that a single use permit has not expired, was authorized for the amount
             * @notice  being transferred, has a valid nonce and has a valid signature.
             * 
             * @dev    Throws when the `nonce` has already been consumed
             * @dev    Throws when the permit amount is less than the transfer amount
             * @dev    Throws when the permit is expired
             * @dev    Throws when the signature is invalid
             * 
             * @param nonce           The nonce of the permit
             * @param expiration      The time the permit expires
             * @param transferAmount  The amount of tokens requested to transfer
             * @param permitAmount    The amount authorized by the owner signature
             * @param owner           The owner of the token
             * @param digest          The digest that was signed by the owner
             * @param signedPermit    The signature for the permit
             */
            function _checkPermitData(
                uint256 nonce,
                uint256 expiration, 
                uint256 transferAmount, 
                uint256 permitAmount, 
                address owner, 
                bytes32 digest,
                bytes calldata signedPermit
            ) private {
                if (block.timestamp > expiration) {
                    revert PermitC__SignatureTransferExceededPermitExpired();
                }
                if (transferAmount > permitAmount) {
                    revert PermitC__SignatureTransferExceededPermittedAmount();
                }
                _checkAndInvalidateNonce(owner, nonce);
                _verifyPermitSignature(digest, signedPermit, owner);
            }
            /**
             * @notice  Stores an approval for future use by `operator` to move tokens on behalf of `owner`
             * 
             * @param tokenType           The type of token
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param amount              The amount authorized by the owner
             * @param expiration          The time the permit expires
             * @param owner               The owner of the token
             * @param operator            The account allowed to transfer the tokens
             */
            function _storeApproval(
                uint256 tokenType,
                address token,
                uint256 id,
                uint200 amount,
                uint48 expiration,
                address owner,
                address operator
            ) private {
                PackedApproval storage approval = _getPackedApprovalPtr(_transferApprovals, owner, tokenType, token, id, ZERO_BYTES32, operator);
                
                approval.expiration = expiration;
                approval.amount = amount;
                emit Approval(owner, token, operator, id, amount, expiration);
            }
            /**
             * @notice  Overload of `_checkOrderTransfer` to supply TOKEN_TYPE_ERC1155
             * 
             * @dev     Prevents stack too deep in `fillPermittedOrderERC1155`
             * @dev     Throws when the order start amount is greater than type(uint200).max
             * @dev     Throws when the order status is not open
             * @dev     Throws when the signature is invalid
             * @dev     Throws when the permit is expired
             * 
             * @param signedPermit        The signature for the permit
             * @param orderFillAmounts    A struct containing the order start, requested fill and minimum fill amounts
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param owner               The owner of the token
             * @param salt                The salt value for the permit
             * @param expiration          The time the permit expires
             * @param orderId             The order id for the permit
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             * 
             * @return orderStatus  Storage pointer for the approval data
             */
            function _checkOrderTransferERC1155(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                uint256 id,
                address owner,
                uint256 salt,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) private returns (PackedApproval storage orderStatus) {
                orderStatus = _checkOrderTransfer(
                    signedPermit,
                    orderFillAmounts,
                    TOKEN_TYPE_ERC1155,
                    token,
                    id,
                    owner,
                    salt,
                    expiration,
                    orderId,
                    advancedPermitHash
                );
            }
            /**
             * @notice  Overload of `_checkOrderTransfer` to supply TOKEN_TYPE_ERC20
             * 
             * @dev     Prevents stack too deep in `fillPermittedOrderERC20`
             * @dev     Throws when the order start amount is greater than type(uint200).max
             * @dev     Throws when the order status is not open
             * @dev     Throws when the signature is invalid
             * @dev     Throws when the permit is expired
             * 
             * @param signedPermit        The signature for the permit
             * @param orderFillAmounts    A struct containing the order start, requested fill and minimum fill amounts
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param owner               The owner of the token
             * @param salt                The salt value for the permit
             * @param expiration          The time the permit expires
             * @param orderId             The order id for the permit
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             * 
             * @return orderStatus  Storage pointer for the approval data
             */
            function _checkOrderTransferERC20(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                uint256 id,
                address owner,
                uint256 salt,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) private returns (PackedApproval storage orderStatus) {
                orderStatus = _checkOrderTransfer(
                    signedPermit,
                    orderFillAmounts,
                    TOKEN_TYPE_ERC20,
                    token,
                    id,
                    owner,
                    salt,
                    expiration,
                    orderId,
                    advancedPermitHash
                );
            }
            /**
             * @notice  Validates an order transfer to check order start amount, status, signature if not previously
             * @notice  opened, and expiration.
             * 
             * @dev     Throws when the order start amount is greater than type(uint200).max
             * @dev     Throws when the order status is not open
             * @dev     Throws when the signature is invalid
             * @dev     Throws when the permit is expired
             * 
             * @param signedPermit        The signature for the permit
             * @param orderFillAmounts    A struct containing the order start, requested fill and minimum fill amounts
             * @param tokenType           The type of token
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param owner               The owner of the token
             * @param salt                The salt value for the permit
             * @param expiration          The time the permit expires
             * @param orderId             The order id for the permit
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             * 
             * @return orderStatus  Storage pointer for the approval data
             */
            function _checkOrderTransfer(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                uint256 tokenType,
                address token,
                uint256 id,
                address owner,
                uint256 salt,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) private returns (PackedApproval storage orderStatus) {
                if (orderFillAmounts.orderStartAmount > type(uint200).max) {
                    revert PermitC__AmountExceedsStorageMaximum();
                }
                orderStatus = _getPackedApprovalPtr(_orderApprovals, owner, tokenType, token, id, orderId, msg.sender);
                if (orderStatus.state == ORDER_STATE_OPEN) {
                    if (orderStatus.amount == 0) {
                        _verifyPermitSignature(
                            _getAdvancedTypedDataV4PermitHash(
                                tokenType,
                                token, 
                                id, 
                                orderFillAmounts.orderStartAmount,
                                owner,
                                salt, 
                                expiration, 
                                orderId, 
                                advancedPermitHash
                            ), 
                            signedPermit, 
                            owner
                        );
                        orderStatus.amount = uint200(orderFillAmounts.orderStartAmount);
                        orderStatus.expiration = expiration;   
                        emit OrderOpened(orderId, owner, msg.sender, orderFillAmounts.orderStartAmount);
                    }
                    if (block.timestamp > orderStatus.expiration) {
                        revert PermitC__SignatureTransferExceededPermitExpired();
                    }
                } else {
                    revert PermitC__OrderIsEitherCancelledOrFilled();
                }
            }
            /**
             * @notice  Checks the order fill amounts against approval data and transfers tokens, updates
             * @notice  approval if the fill results in the order being closed.
             * 
             * @dev     Throws when the amount to fill is less than the minimum fill amount
             * 
             * @param orderStatus         Storage pointer for the approval data
             * @param orderFillAmounts    A struct containing the order start, requested fill and minimum fill amounts
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param owner               The owner of the token
             * @param to                  The address to send the tokens to
             * @param orderId             The order id for the permit
             * @param _transferFrom       Function pointer of the transfer function to send tokens with
             * 
             * @return quantityFilled     The number of tokens filled in the order
             * @return isError            True if there was an error transferring tokens, false otherwise
             */
            function _orderTransfer(
                PackedApproval storage orderStatus,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                uint256 id,
                address owner,
                address to,
                bytes32 orderId,
                function (address, address, address, uint256, uint256) internal returns (bool) _transferFrom
            ) private returns (uint256 quantityFilled, bool isError) {
                quantityFilled = orderFillAmounts.requestedFillAmount;
                
                if (quantityFilled > orderStatus.amount) {
                    quantityFilled = orderStatus.amount;
                }
                if (quantityFilled < orderFillAmounts.minimumFillAmount) {
                    revert PermitC__UnableToFillMinimumRequestedQuantity();
                }
                unchecked {
                    orderStatus.amount -= uint200(quantityFilled);
                    emit OrderFilled(orderId, owner, msg.sender, quantityFilled);
                }
                if (orderStatus.amount == 0) {
                    orderStatus.state = ORDER_STATE_FILLED;
                    emit OrderClosed(orderId, owner, msg.sender, false);
                }
                isError = _transferFrom(token, owner, to, id, quantityFilled);
            }
            /**
             * @notice  Restores an account's nonce when a transfer was not successful
             * 
             * @dev     Throws when the nonce was not already consumed
             * 
             * @param account  The account to restore the nonce of
             * @param nonce    The nonce to restore
             */
            function _restoreNonce(address account, uint256 nonce) private {
                unchecked {
                    if (uint256(_unorderedNonces[account][uint248(nonce >> 8)] ^= (ONE << uint8(nonce))) & 
                        (ONE << uint8(nonce)) != ZERO) {
                        revert PermitC__NonceNotUsedOrRevoked();
                    }
                }
            }
            /**
             * @notice  Restores an approval amount when a transfer was not successful
             * 
             * @param approval        Storage pointer for the approval data
             * @param owner           The owner of the tokens
             * @param orderId         The order id to restore approval amount on
             * @param unfilledAmount  The amount that was not filled on the order
             * @param isOrderPermit   True if the fill restoration is for an permit order
             */
            function _restoreFillableItems(
                PackedApproval storage approval,
                address owner,
                bytes32 orderId,
                uint256 unfilledAmount,
                bool isOrderPermit
            ) private {
                if (unfilledAmount > 0) {
                    if (isOrderPermit) {
                        // Order permits always deduct amount and must be restored
                        unchecked {
                            approval.amount += uint200(unfilledAmount);
                        }
                        approval.state = ORDER_STATE_OPEN;
                        emit OrderRestored(orderId, owner, unfilledAmount);
                    } else if (approval.amount < type(uint200).max) {
                        // Stored approvals only deduct amount 
                        unchecked {
                            approval.amount += uint200(unfilledAmount);
                        }
                    }
                }
            }
            function _requireValidTokenType(uint256 tokenType) private pure {
                if(!(
                    tokenType == TOKEN_TYPE_ERC721 || 
                    tokenType == TOKEN_TYPE_ERC1155 || 
                    tokenType == TOKEN_TYPE_ERC20
                    )
                ) {
                    revert PermitC__InvalidTokenType();
                }
            }
            /**
             * @notice  Generates an EIP-712 digest for a permit
             * 
             * @param tokenType           The type of token
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param amount              The amount authorized by the owner signature
             * @param owner               The owner of the token
             * @param nonce               The nonce for the permit
             * @param expiration          The time the permit expires
             * @param additionalData      The additional data to validate with the permit signature
             * @param advancedPermitHash  The typehash of the permit to use for validating the signature
             * 
             * @return digest  The EIP-712 digest of the permit data
             */
            function _getAdvancedTypedDataV4PermitHash(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 amount,
                address owner,
                uint256 nonce,
                uint256 expiration,
                bytes32 additionalData,
                bytes32 advancedPermitHash
            ) private view returns (bytes32 digest) {
                // cache masterNonce on stack to avoid stack too deep
                uint256 masterNonce_ = _masterNonces[owner];
                digest = 
                    _hashTypedDataV4(
                        PermitHash.hashSingleUsePermitWithAdditionalData(
                            tokenType,
                            token, 
                            id, 
                            amount, 
                            nonce, 
                            expiration, 
                            additionalData, 
                            advancedPermitHash, 
                            masterNonce_
                        )
                    );
            }
            /**
             * @notice  Returns the current allowed amount and expiration for a stored permit
             * 
             * @dev     Returns zero allowed if the permit has expired
             * 
             * @param _approvals  The mapping to retrieve the approval from
             * @param owner       The account the approval is from
             * @param operator    The operator for the approval
             * @param tokenType   The type of token the approval is for
             * @param token       The address of the token
             * @param id          The id of the token
             * @param orderId     The order id for the approval
             * 
             * @return allowedAmount  The amount authorized by the approval, zero if the permit has expired
             * @return expiration     The expiration of the approval
             */
            function _allowance(
                mapping(bytes32 => mapping(address => PackedApproval)) storage _approvals,
                address owner, 
                address operator, 
                uint256 tokenType, 
                address token, 
                uint256 id, 
                bytes32 orderId
            ) private view returns (uint256 allowedAmount, uint256 expiration) {
                PackedApproval storage allowed = _getPackedApprovalPtr(_approvals, owner, tokenType, token, id, orderId, operator);
                allowedAmount = allowed.expiration < block.timestamp ? 0 : allowed.amount;
                expiration = allowed.expiration;
            }
            /**
             * @notice  Allows the owner of the PermitC contract to access pausable admin functions
             * 
             * @dev     May be overriden by an inheriting contract to provide alternative permission structure
             */
            function _requireCallerHasPausePermissions() internal view virtual override {
                _checkOwner();
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.4;
        import {OrderFillAmounts} from "../DataTypes.sol";
        interface IPermitC {
            /**
             * =================================================
             * ==================== Events =====================
             * =================================================
             */
            /// @dev Emitted when an approval is stored
            event Approval(
                address indexed owner,
                address indexed token,
                address indexed operator,
                uint256 id,
                uint200 amount,
                uint48 expiration
            );
            /// @dev Emitted when a user increases their master nonce
            event Lockdown(address indexed owner);
            /// @dev Emitted when an order is opened
            event OrderOpened(
                bytes32 indexed orderId,
                address indexed owner,
                address indexed operator,
                uint256 fillableQuantity
            );
            /// @dev Emitted when an order has a fill
            event OrderFilled(
                bytes32 indexed orderId,
                address indexed owner,
                address indexed operator,
                uint256 amount
            );
            /// @dev Emitted when an order has been fully filled or cancelled
            event OrderClosed(
                bytes32 indexed orderId, 
                address indexed owner, 
                address indexed operator, 
                bool wasCancellation);
            /// @dev Emitted when an order has an amount restored due to a failed transfer
            event OrderRestored(
                bytes32 indexed orderId,
                address indexed owner,
                uint256 amountRestoredToOrder
            );
            /**
             * =================================================
             * ============== Approval Transfers ===============
             * =================================================
             */
            function approve(uint256 tokenType, address token, uint256 id, address operator, uint200 amount, uint48 expiration) external;
            function updateApprovalBySignature(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 nonce,
                uint200 amount,
                address operator,
                uint48 approvalExpiration,
                uint48 sigDeadline,
                address owner,
                bytes calldata signedPermit
            ) external;
            function allowance(
                address owner, 
                address operator, 
                uint256 tokenType,
                address token, 
                uint256 id
            ) external view returns (uint256 amount, uint256 expiration);
            /**
             * =================================================
             * ================ Signed Transfers ===============
             * =================================================
             */
            function registerAdditionalDataHash(string memory additionalDataTypeString) external;
            function permitTransferFromERC721(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 expiration,
                address owner,
                address to,
                bytes calldata signedPermit
            ) external returns (bool isError);
            function permitTransferFromWithAdditionalDataERC721(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 expiration,
                address owner,
                address to,
                bytes32 additionalData,
                bytes32 advancedPermitHash,
                bytes calldata signedPermit
            ) external returns (bool isError);
            function permitTransferFromERC1155(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes calldata signedPermit
            ) external returns (bool isError);
            function permitTransferFromWithAdditionalDataERC1155(
                address token,
                uint256 id,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes32 additionalData,
                bytes32 advancedPermitHash,
                bytes calldata signedPermit
            ) external returns (bool isError);
            function permitTransferFromERC20(
                address token,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes calldata signedPermit
            ) external returns (bool isError);
            function permitTransferFromWithAdditionalDataERC20(
                address token,
                uint256 nonce,
                uint256 permitAmount,
                uint256 expiration,
                address owner,
                address to,
                uint256 transferAmount,
                bytes32 additionalData,
                bytes32 advancedPermitHash,
                bytes calldata signedPermit
            ) external returns (bool isError);
            function isRegisteredTransferAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered);
            function isRegisteredOrderAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered);
            /**
             * =================================================
             * =============== Order Transfers =================
             * =================================================
             */
            function fillPermittedOrderERC1155(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                uint256 id,
                address owner,
                address to,
                uint256 nonce,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) external returns (uint256 quantityFilled, bool isError);
            function fillPermittedOrderERC20(
                bytes calldata signedPermit,
                OrderFillAmounts calldata orderFillAmounts,
                address token,
                address owner,
                address to,
                uint256 nonce,
                uint48 expiration,
                bytes32 orderId,
                bytes32 advancedPermitHash
            ) external returns (uint256 quantityFilled, bool isError);
            function closePermittedOrder(
                address owner,
                address operator,
                uint256 tokenType,
                address token,
                uint256 id,
                bytes32 orderId
            ) external;
            function allowance(
                address owner, 
                address operator, 
                uint256 tokenType,
                address token, 
                uint256 id,
                bytes32 orderId
            ) external view returns (uint256 amount, uint256 expiration);
            /**
             * =================================================
             * ================ Nonce Management ===============
             * =================================================
             */
            function invalidateUnorderedNonce(uint256 nonce) external;
            function isValidUnorderedNonce(address owner, uint256 nonce) external view returns (bool isValid);
            function lockdown() external;
            function masterNonce(address owner) external view returns (uint256);
            /**
             * =================================================
             * ============== Transfer Functions ===============
             * =================================================
             */
            function transferFromERC721(
                address from,
                address to,
                address token,
                uint256 id
            ) external returns (bool isError);
            function transferFromERC1155(
                address from,
                address to,
                address token,
                uint256 id,
                uint256 amount
            ) external returns (bool isError);
            function transferFromERC20(
                address from,
                address to,
                address token,
                uint256 amount
            ) external returns (bool isError);
            /**
             * =================================================
             * ============ Signature Verification =============
             * =================================================
             */
            function domainSeparatorV4() external view returns (bytes32);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.24;
        import {SINGLE_USE_PERMIT_TYPEHASH, UPDATE_APPROVAL_TYPEHASH} from "../Constants.sol";
        library PermitHash {
            /**
             * @notice  Hashes the permit data for a stored approval
             * 
             * @param tokenType           The type of token
             * @param token               The address of the token
             * @param id                  The id of the token
             * @param amount              The amount authorized by the owner signature
             * @param nonce               The nonce for the permit
             * @param operator            The account that is allowed to use the permit
             * @param approvalExpiration  The time the permit approval expires
             * @param sigDeadline         The deadline for submitting the permit onchain
             * @param masterNonce         The signers master nonce
             * 
             * @return hash  The hash of the permit data
             */
            function hashOnChainApproval(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 amount,
                uint256 nonce,
                address operator, 
                uint256 approvalExpiration,
                uint256 sigDeadline,
                uint256 masterNonce
            ) internal pure returns (bytes32 hash) {
                hash = keccak256(
                    abi.encode(
                        UPDATE_APPROVAL_TYPEHASH,
                        tokenType,
                        token,
                        id,
                        amount,
                        nonce,
                        operator,
                        approvalExpiration,
                        sigDeadline,
                        masterNonce
                    )
                );
            }
            /**
             * @notice  Hashes the permit data with the single user permit without additional data typehash
             * 
             * @param tokenType               The type of token
             * @param token                   The address of the token
             * @param id                      The id of the token
             * @param amount                  The amount authorized by the owner signature
             * @param nonce                   The nonce for the permit
             * @param expiration              The time the permit expires
             * @param masterNonce             The signers master nonce
             * 
             * @return hash  The hash of the permit data
             */
            function hashSingleUsePermit(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 amount,
                uint256 nonce,
                uint256 expiration,
                uint256 masterNonce
            ) internal view returns (bytes32 hash) {
                hash = keccak256(
                    abi.encode(
                        SINGLE_USE_PERMIT_TYPEHASH,
                        tokenType,
                        token,
                        id,
                        amount,
                        nonce,
                        msg.sender,
                        expiration,
                        masterNonce
                    )
                );
            }
            /**
             * @notice  Hashes the permit data with the supplied typehash
             * 
             * @param tokenType               The type of token
             * @param token                   The address of the token
             * @param id                      The id of the token
             * @param amount                  The amount authorized by the owner signature
             * @param nonce                   The nonce for the permit
             * @param expiration              The time the permit expires
             * @param additionalData          The additional data to validate with the permit signature
             * @param additionalDataTypeHash  The typehash of the permit to use for validating the signature
             * @param masterNonce             The signers master nonce
             * 
             * @return hash  The hash of the permit data with the supplied typehash
             */
            function hashSingleUsePermitWithAdditionalData(
                uint256 tokenType,
                address token,
                uint256 id,
                uint256 amount,
                uint256 nonce,
                uint256 expiration,
                bytes32 additionalData,
                bytes32 additionalDataTypeHash,
                uint256 masterNonce
            ) internal view returns (bytes32 hash) {
                hash = keccak256(
                    abi.encode(
                        additionalDataTypeHash,
                        tokenType,
                        token,
                        id,
                        amount,
                        nonce,
                        msg.sender,
                        expiration,
                        masterNonce,
                        additionalData
                    )
                );
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol)
        pragma solidity ^0.8.8;
        import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
        /**
         * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
         *
         * The encoding specified in the EIP is very generic, and such a generic 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 their contracts 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: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
         * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the
         * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
         *
         * _Available since v3.4._
         *
         * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
         */
        abstract contract EIP712 {
            bytes32 private constant _TYPE_HASH =
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
            // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
            // invalidate the cached domain separator if the chain id changes.
            bytes32 private immutable _cachedDomainSeparator;
            uint256 private immutable _cachedChainId;
            bytes32 private immutable _hashedName;
            bytes32 private immutable _hashedVersion;
            /**
             * @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].
             */
            constructor(string memory name, string memory version) {
                _hashedName = keccak256(bytes(name));
                _hashedVersion = keccak256(bytes(version));
                _cachedChainId = block.chainid;
                _cachedDomainSeparator = _buildDomainSeparator();
            }
            /**
             * @dev Returns the domain separator for the current chain.
             */
            function _domainSeparatorV4() internal view returns (bytes32) {
                if (block.chainid == _cachedChainId) {
                    return _cachedDomainSeparator;
                } else {
                    return _buildDomainSeparator();
                }
            }
            function _buildDomainSeparator() private view returns (bytes32) {
                return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, 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 ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
        pragma solidity ^0.8.0;
        import {Context} from "@openzeppelin/contracts/utils/Context.sol";
        /**
         * @dev Contract module which provides a basic access control mechanism, where
         * there is an account (an owner) that can be granted exclusive access to
         * specific functions.
         *
         * By default, the owner account will be the one that deploys the contract. This
         * can later be changed with {transferOwnership}.
         *
         * This module is used through inheritance. It will make available the modifier
         * `onlyOwner`, which can be applied to your functions to restrict their use to
         * the owner.
         */
        abstract contract Ownable is Context {
            error Ownable__CallerIsNotOwner();
            error Ownable__NewOwnerIsZeroAddress();
            address private _owner;
            event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            /**
             * @dev Initializes the contract setting the deployer as the initial owner.
             */
            constructor() {
                _transferOwnership(_msgSender());
            }
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
                _checkOwner();
                _;
            }
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() public view virtual returns (address) {
                return _owner;
            }
            /**
             * @dev Throws if the sender is not the owner.
             */
            function _checkOwner() internal view virtual {
                if(owner() != _msgSender()) revert Ownable__CallerIsNotOwner();
            }
            /**
             * @dev Leaves the contract without owner. It will not be possible to call
             * `onlyOwner` functions. Can only be called by the current owner.
             *
             * NOTE: Renouncing ownership will leave the contract without an owner,
             * thereby disabling any functionality that is only available to the owner.
             */
            function renounceOwnership() public virtual onlyOwner {
                _transferOwnership(address(0));
            }
            /**
             * @dev Transfers ownership of the contract to a new account (`newOwner`).
             * Can only be called by the current owner.
             */
            function transferOwnership(address newOwner) public virtual onlyOwner {
                if(newOwner == address(0)) revert Ownable__NewOwnerIsZeroAddress();
                _transferOwnership(newOwner);
            }
            /**
             * @dev Transfers ownership of the contract to a new account (`newOwner`).
             * Internal function without access restriction.
             */
            function _transferOwnership(address newOwner) internal virtual {
                address oldOwner = _owner;
                _owner = newOwner;
                emit OwnershipTransferred(oldOwner, newOwner);
            }
        }
        pragma solidity ^0.8.4;
        import "./IERC165.sol";
        /**
         * @dev Implementation of the {IERC165} interface.
         *
         * Contracts that want to implement ERC-165 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);
         * }
         * ```
         */
        abstract contract ERC165 is IERC165 {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
                return interfaceId == type(IERC165).interfaceId;
            }
        }pragma solidity ^0.8.4;
        /**
         * @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);
        }pragma solidity ^0.8.24;
        library StorageTstorish {   
            // keccak256(abi.encode(uint256(keccak256("storage.Tstorish")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant DATA_STORAGE_SLOT = 
                0xdacd49f6a6c42b45a5c3d195b83b324104542d9147bb8064a39c6a8d23ba9b00;
            struct Data {
                // Indicates if TSTORE support has been activated during or post-deployment.
                bool tstoreSupport;
            }
            function data() internal pure returns (Data storage ptr) {
                bytes32 slot = DATA_STORAGE_SLOT;
                assembly {
                    ptr.slot := slot
                }
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.24;
        import "./StorageTstorish.sol";
        /**
         * @title  Tstorish
         * @notice Based on https://github.com/ProjectOpenSea/tstorish/commit/a81ed74453ed7b9fe7e96a9906bc4def19b73e33
         */
        abstract contract Tstorish {
            /*
             * ------------------------------------------------------------------------+
             * Opcode      | Mnemonic         | Stack              | Memory            |
             * ------------------------------------------------------------------------|
             * 60 0x02     | PUSH1 0x02       | 0x02               |                   |
             * 60 0x1e     | PUSH1 0x1e       | 0x1e 0x02          |                   |
             * 61 0x3d5c   | PUSH2 0x3d5c     | 0x3d5c 0x1e 0x02   |                   |
             * 3d          | RETURNDATASIZE   | 0 0x3d5c 0x1e 0x02 |                   |
             *                                                                         |
             * :: store deployed bytecode in memory: (3d) RETURNDATASIZE (5c) TLOAD :: |
             * 52          | MSTORE           | 0x1e 0x02          | [0..0x20): 0x3d5c |
             * f3          | RETURN           |                    | [0..0x20): 0x3d5c |
             * ------------------------------------------------------------------------+
             */
            uint256 constant _TLOAD_TEST_PAYLOAD = 0x6002_601e_613d5c_3d_52_f3;
            uint256 constant _TLOAD_TEST_PAYLOAD_LENGTH = 0x0a;
            uint256 constant _TLOAD_TEST_PAYLOAD_OFFSET = 0x16;
            // Declare an immutable variable to store the tstore test contract address.
            address private immutable _tloadTestContract;
            // Declare an immutable variable to store the initial TSTORE support status.
            bool internal immutable _tstoreInitialSupport;
            // Declare an immutable function type variable for the _setTstorish function
            // based on chain support for tstore at time of deployment.
            function(uint256,uint256) internal immutable _setTstorish;
            // Declare an immutable function type variable for the _getTstorish function
            // based on chain support for tstore at time of deployment.
            function(uint256) view returns (uint256) internal immutable _getTstorish;
            // Declare an immutable function type variable for the _clearTstorish function
            // based on chain support for tstore at time of deployment.
            function(uint256) internal immutable _clearTstorish;
            // Declare a few custom revert error types.
            error TStoreAlreadyActivated();
            error TStoreNotSupported();
            error TloadTestContractDeploymentFailed();
            error OnlyDirectCalls();
            /**
             * @dev Determine TSTORE availability during deployment. This involves
             *      attempting to deploy a contract that utilizes TLOAD as part of the
             *      contract construction bytecode, and configuring initial support for
             *      using TSTORE in place of SSTORE based on the result.
             */
            constructor() {
                // Deploy the contract testing TLOAD support and store the address.
                address tloadTestContract = _prepareTloadTest();
                // Ensure the deployment was successful.
                if (tloadTestContract == address(0)) {
                    revert TloadTestContractDeploymentFailed();
                }
                // Determine if TSTORE is supported.
                _tstoreInitialSupport = StorageTstorish.data().tstoreSupport = _testTload(tloadTestContract);
                if (_tstoreInitialSupport) {
                    // If TSTORE is supported, set functions to their versions that use
                    // tstore/tload directly without support checks.
                    _setTstorish = _setTstore;
                    _getTstorish = _getTstore;
                    _clearTstorish = _clearTstore;
                } else {
                    // If TSTORE is not supported, set functions to their versions that 
                    // fallback to sstore/sload until tstoreSupport is true.
                    _setTstorish = _setTstorishWithSstoreFallback;
                    _getTstorish = _getTstorishWithSloadFallback;
                    _clearTstorish = _clearTstorishWithSstoreFallback;
                }
                // Set the address of the deployed TLOAD test contract as an immutable.
                _tloadTestContract = tloadTestContract;
            }
            /**
             * @dev Called internally when tstore is activated by an external call to 
             *      `__activateTstore`. Developers must override this function and handle
             *      relevant transfers of data from regular storage to transient storage *OR*
             *      revert the transaction if it is in a state that should not support the activation
             *      of tstore.
             */
            function _onTstoreSupportActivated() internal virtual;
            /**
             * @dev External function to activate TSTORE usage. Does not need to be
             *      called if TSTORE is supported from deployment, and only needs to be
             *      called once. Reverts if TSTORE has already been activated or if the
             *      opcode is not available. Note that this must be called directly from
             *      an externally-owned account to avoid potential reentrancy issues.
             */
            function __activateTstore() external {
                // Determine if TSTORE can potentially be activated.
                if (_tstoreInitialSupport || StorageTstorish.data().tstoreSupport) {
                    revert TStoreAlreadyActivated();
                }
                // Determine if TSTORE can be activated and revert if not.
                if (!_testTload(_tloadTestContract)) {
                    revert TStoreNotSupported();
                }
                // Mark TSTORE as activated.
                StorageTstorish.data().tstoreSupport = true;
                _onTstoreSupportActivated();
            }
            /**
             * @dev Private function to set a TSTORISH value. Assigned to _setTstorish 
             *      internal function variable at construction if chain has tstore support.
             *
             * @param storageSlot The slot to write the TSTORISH value to.
             * @param value       The value to write to the given storage slot.
             */
            function _setTstore(uint256 storageSlot, uint256 value) internal {
                assembly {
                    tstore(storageSlot, value)
                }
            }
            /**
             * @dev Private function to set a TSTORISH value with sstore fallback. 
             *      Assigned to _setTstorish internal function variable at construction
             *      if chain does not have tstore support.
             *
             * @param storageSlot The slot to write the TSTORISH value to.
             * @param value       The value to write to the given storage slot.
             */
            function _setTstorishWithSstoreFallback(uint256 storageSlot, uint256 value) internal {
                if (StorageTstorish.data().tstoreSupport) {
                    assembly {
                        tstore(storageSlot, value)
                    }
                } else {
                    assembly {
                        sstore(storageSlot, value)
                    }
                }
            }
            /**
             * @dev Private function to read a TSTORISH value. Assigned to _getTstorish
             *      internal function variable at construction if chain has tstore support.
             *
             * @param storageSlot The slot to read the TSTORISH value from.
             *
             * @return value The TSTORISH value at the given storage slot.
             */
            function _getTstore(
                uint256 storageSlot
            ) internal view returns (uint256 value) {
                assembly {
                    value := tload(storageSlot)
                }
            }
            /**
             * @dev Private function to read a TSTORISH value with sload fallback. 
             *      Assigned to _getTstorish internal function variable at construction
             *      if chain does not have tstore support.
             *
             * @param storageSlot The slot to read the TSTORISH value from.
             *
             * @return value The TSTORISH value at the given storage slot.
             */
            function _getTstorishWithSloadFallback(
                uint256 storageSlot
            ) internal view returns (uint256 value) {
                if (StorageTstorish.data().tstoreSupport) {
                    assembly {
                        value := tload(storageSlot)
                    }
                } else {
                    assembly {
                        value := sload(storageSlot)
                    }
                }
            }
            /**
             * @dev Private function to clear a TSTORISH value. Assigned to _clearTstorish internal 
             *      function variable at construction if chain has tstore support.
             *
             * @param storageSlot The slot to clear the TSTORISH value for.
             */
            function _clearTstore(uint256 storageSlot) internal {
                assembly {
                    tstore(storageSlot, 0)
                }
            }
            /**
             * @dev Private function to clear a TSTORISH value with sstore fallback. 
             *      Assigned to _clearTstorish internal function variable at construction
             *      if chain does not have tstore support.
             *
             * @param storageSlot The slot to clear the TSTORISH value for.
             */
            function _clearTstorishWithSstoreFallback(uint256 storageSlot) internal {
                if (StorageTstorish.data().tstoreSupport) {
                    assembly {
                        tstore(storageSlot, 0)
                    }
                } else {
                    assembly {
                        sstore(storageSlot, 0)
                    }
                }
            }
            /**
             * @dev Private function to copy a value from storage to transient storage at the same slot.
             *      Useful when tstore is activated on a chain that didn't initially support it.
             */
            function _copyFromStorageToTransient(uint256 storageSlot) internal {
                if (StorageTstorish.data().tstoreSupport) {
                    assembly {
                        tstore(storageSlot, sload(storageSlot))
                    }
                } else {
                    revert TStoreNotSupported();
                }
            }
            /**
             * @dev Private function to deploy a test contract that utilizes TLOAD as
             *      part of its fallback logic.
             */
            function _prepareTloadTest() private returns (address contractAddress) {
                // Utilize assembly to deploy a contract testing TLOAD support.
                assembly {
                    // Write the contract deployment code payload to scratch space.
                    mstore(0, _TLOAD_TEST_PAYLOAD)
                    // Deploy the contract.
                    contractAddress := create(
                        0,
                        _TLOAD_TEST_PAYLOAD_OFFSET,
                        _TLOAD_TEST_PAYLOAD_LENGTH
                    )
                }
            }
            /**
             * @dev Private view function to determine if TSTORE/TLOAD are supported by
             *      the current EVM implementation by attempting to call the test
             *      contract, which utilizes TLOAD as part of its fallback logic.
             */
            function _testTload(
                address tloadTestContract
            ) private view returns (bool ok) {
                // Call the test contract, which will perform a TLOAD test. If the call
                // does not revert, then TLOAD/TSTORE is supported. Do not forward all
                // available gas, as all forwarded gas will be consumed on revert.
                (ok, ) = tloadTestContract.staticcall{ gas: gasleft() / 10 }("");
            }
        }pragma solidity ^0.8.4;
        /**
         * @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.
         *
         * ```solidity
         * contract Example {
         *     // Add the library methods
         *     using EnumerableSet for EnumerableSet.AddressSet;
         *
         *     // Declare a set state variable
         *     EnumerableSet.AddressSet private mySet;
         * }
         * ```
         *
         * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
         * and `uint256` (`UintSet`) are supported.
         *
         * [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 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;
            }
            // 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 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;
                /// @solidity memory-safe-assembly
                assembly {
                    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 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;
                /// @solidity memory-safe-assembly
                assembly {
                    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 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;
                /// @solidity memory-safe-assembly
                assembly {
                    result := store
                }
                return result;
            }
        }pragma solidity ^0.8.4;
        import "../introspection/IERC165.sol";
        interface IEOARegistry is IERC165 {
            function isVerifiedEOA(address account) external view returns (bool);
        }pragma solidity ^0.8.4;
        interface ITransferValidator {
            function applyCollectionTransferPolicy(address caller, address from, address to) external view;
            function validateTransfer(address caller, address from, address to) external view;
            function validateTransfer(address caller, address from, address to, uint256 tokenId) external view;
            function validateTransfer(address caller, address from, address to, uint256 tokenId, uint256 amount) external;
            function beforeAuthorizedTransfer(address operator, address token, uint256 tokenId) external;
            function afterAuthorizedTransfer(address token, uint256 tokenId) external;
            function beforeAuthorizedTransfer(address operator, address token) external;
            function afterAuthorizedTransfer(address token) external;
            function beforeAuthorizedTransfer(address token, uint256 tokenId) external;
            function beforeAuthorizedTransferWithAmount(address token, uint256 tokenId, uint256 amount) external;
            function afterAuthorizedTransferWithAmount(address token, uint256 tokenId) external;
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /**************************************************************/
        /*                        LIST TYPES                          */
        /**************************************************************/
        uint8 constant LIST_TYPE_BLACKLIST = 0;
        uint8 constant LIST_TYPE_WHITELIST = 1;
        uint8 constant LIST_TYPE_AUTHORIZERS = 2;
        uint8 constant EXPANSION_LIST_TYPE_WHITELIST_EXTENSION_CONTRACTS = 3;
        uint8 constant EXPANSION_LIST_TYPE_7702_DELEGATE_WHITELIST = 4;
        uint8 constant EXPANSION_LIST_TYPE_7702_DELEGATE_WHITELIST_EXTENSION_CONTRACTS = 5;
        /**************************************************************/
        /*                        RULESET IDS                         */
        /**************************************************************/
        uint8 constant RULESET_ID_DEFAULT = 0;
        uint8 constant RULESET_ID_VANILLA = 1;
        uint8 constant RULESET_ID_SOULBOUND = 2;
        uint8 constant RULESET_ID_BLACKLIST = 3;
        uint8 constant RULESET_ID_WHITELIST = 4;
        uint8 constant RULESET_ID_FIXED_OR_CUSTOM = 255;
        /**************************************************************/
        /*                    AUTHORIZER CHECK TYPES                  */
        /**************************************************************/
        uint256 constant AUTHORIZER_CHECK_TYPE_TOKEN = 1;
        uint256 constant AUTHORIZER_CHECK_TYPE_COLLECTION = 2;
        uint256 constant AUTHORIZER_CHECK_TYPE_TOKEN_AND_AMOUNT = 3;
        /**************************************************************/
        /*                       MISCELLANEOUS                        */
        /**************************************************************/
        bytes4 constant LEGACY_TRANSFER_VALIDATOR_INTERFACE_ID = bytes4(0x00000000);
        bytes4 constant SELECTOR_NO_ERROR = bytes4(0x00000000);
        bytes32 constant BYTES32_ZERO = 0x0000000000000000000000000000000000000000000000000000000000000000;
        bytes32 constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x0000000000000000000000000000000000000000000000000000000000000000;
        address constant WILDCARD_OPERATOR_ADDRESS = address(0x01);
        uint48 constant DEFAULT_LIST_ID = 0;
        uint16 constant DEFAULT_TOKEN_TYPE = 0;
        /**************************************************************/
        /*                       FLAGS - GLOBAL                       */
        /**************************************************************/
        // Flags are used to efficiently store and retrieve boolean values in a single uint8.
        // Each flag is a power of 2, so they can be combined using bitwise OR (|) and checked using bitwise AND (&).
        // For example, to set the first and third flags, you would use: flags = FLAG1 | FLAG3;
        // To check if the first flag is set, you would use: if (flags & FLAG1 != 0) { ... }
        uint8 constant FLAG_GLOBAL_DISABLE_AUTHORIZATION_MODE = 1 << 0;
        uint8 constant FLAG_GLOBAL_AUTHORIZERS_CANNOT_SET_WILDCARD_OPERATORS = 1 << 1;
        uint8 constant FLAG_GLOBAL_ENABLE_ACCOUNT_FREEZING_MODE = 1 << 2;
        uint8 constant FLAG_GLOBAL_CUSTOM_LIST_SUPPLEMENTS_DEFAULT_LIST = 1 << 3;
        /**************************************************************/
        /*                  FLAGS - RULESET WHITELIST                 */
        /**************************************************************/
        uint16 constant FLAG_RULESET_WHITELIST_BLOCK_ALL_OTC = 1 << 0;
        uint16 constant FLAG_RULESET_WHITELIST_ALLOW_OTC_FOR_7702_DELEGATES = 1 << 1;
        uint16 constant FLAG_RULESET_WHITELIST_ALLOW_OTC_FOR_SMART_WALLETS = 1 << 2;
        uint16 constant FLAG_RULESET_WHITELIST_BLOCK_SMART_WALLET_RECEIVERS = 1 << 3;
        uint16 constant FLAG_RULESET_WHITELIST_BLOCK_UNVERIFIED_EOA_RECEIVERS = 1 << 4;// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "./Constants.sol";
        import "./DataTypes.sol";
        import "./Errors.sol";
        import "./ValidatorBase.sol";
        import "./interfaces/IRuleset.sol";
        import "./interfaces/IRulesetDelegateCall.sol";
        import "@limitbreak/permit-c/PermitC.sol";
        import "@limitbreak/tm-core-lib/src/utils/introspection/ERC165.sol";
        import "@limitbreak/tm-core-lib/src/utils/structs/EnumerableSet.sol";
        import "@limitbreak/tm-core-lib/src/utils/token/IEOARegistry.sol";
        import "@limitbreak/tm-core-lib/src/utils/token/ITransferValidator.sol";
        import "@limitbreak/tm-core-lib/src/utils/misc/Tstorish.sol";
        /**
         * @title  CreatorTokenTransferValidator
         * @author Limit Break, Inc.
         * @notice The CreatorTokenTransferValidator contract is designed to provide a customizable and secure transfer 
         *         validation mechanism for NFT collections. This contract allows the owner of an NFT collection to choose a 
         *         validation ruleset to apply to their collection, global and ruleset-specific options to fine-tune validation
         *         behavior, and manage blacklists/whitelists/authorizer/expansion lists and expansion settings for rulesets.
         */
        contract CreatorTokenTransferValidator is 
        IEOARegistry, 
        ITransferValidator, 
        ValidatorBase, 
        ERC165, 
        Tstorish, 
        PermitC {
            using EnumerableSet for EnumerableSet.AddressSet;
            using EnumerableSet for EnumerableSet.Bytes32Set;
            /*************************************************************************/
            /*                                EVENTS                                 */
            /*************************************************************************/
            /// @dev Emitted when a new list is created.
            event CreatedList(uint256 indexed id, string name);
            /// @dev Emitted when the ownership of a list is transferred to a new owner.
            event ReassignedListOwnership(uint256 indexed id, address indexed newOwner);
            /*************************************************************************/
            /*                               CONSTANTS                               */
            /*************************************************************************/
            /// @dev The address of the EOA Registry to use to validate an account is a verified EOA.
            address private immutable _eoaRegistry;
            /// @dev The address of the management module that implements list and collection settings management.
            address private immutable _managementModule;
            /// @dev The address of the safe delegate module that implements the safe delegate code checker.
            address private immutable _safeDelegateModule;
            constructor(
                address defaultOwner,
                address eoaRegistry_,
                address managementModule_,
                address safeDelegateModule_,
                string memory name,
                string memory version
            ) 
            Tstorish()
            PermitC(
                name,
                version,
                defaultOwner,
                block.chainid == 1 ? 0.33 ether : 0.01 ether
            ) {
                if (defaultOwner == address(0) || 
                    eoaRegistry_ == address(0) || eoaRegistry_.code.length == 0 ||
                    managementModule_ == address(0) || managementModule_.code.length == 0 ||
                    safeDelegateModule_ == address(0) || safeDelegateModule_.code.length == 0) {
                    revert CreatorTokenTransferValidator__InvalidConstructorArgs();
                }
                _createDefaultList(defaultOwner);
                _eoaRegistry = eoaRegistry_;
                _managementModule = managementModule_;
                _safeDelegateModule = safeDelegateModule_;
            }
            /**
             * @dev This function is only called during contract construction to create the default list.
             */
            function _createDefaultList(address defaultOwner) internal {
                uint48 id = 0;
                validatorStorage().listOwners[id] = defaultOwner;
                emit CreatedList(id, "DEFAULT LIST");
                emit ReassignedListOwnership(id, defaultOwner);
            }
            /*************************************************************************/
            /*                               MODIFIERS                               */
            /*************************************************************************/
            /**
             * @dev This modifier delegatecalls the specified module with the exact same calldata
             *      that came into the function, properly handling returndata when necessary.
             *      Assumes that the complete implementation of a function call is done in an external
             *      module.
             */
            modifier delegateCall(address module) {
                assembly {
                    calldatacopy(0, 0, calldatasize())
                    let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0)
                    let size := returndatasize()
                    returndatacopy(0, 0, size)
                    if iszero(result) {
                        revert(0, size)
                    }
                    return(0, size)
                }
                _;
            }
            /*************************************************************************/
            /*                               RULESET ADMIN                           */
            /*************************************************************************/
            /**
             * @notice Registers a ruleset to be used for transfer validation.
             * 
             * @notice The ruleset must be a contract and goes through a sanitization / purity check process.
             *         The ruleset may not contain any banned opcodes (either undefined as of Prague or any known opcode that
             *         carries any risk of making state changes on the EVM). 
             *
             * @dev    Throws if the `msg.sender` does not own the default list (validator admin).
             * @dev    Throws if codehash of the ruleset denotes that it is either an uninitialized account or is an EOA.
             * @dev    Throws if the ruleset is not pure (meaning that it contains an invalid or banned opcode).
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The ruleset is marked as registered in the validator storage.
             *      2. A `RulesetRegistered` event is emitted, the first time the ruleset is registered.
             *         (Subsequent registrations of the same ruleset do not emit this event).
             *
             * @param ruleset The address of the ruleset to register.
             */
            function registerRuleset(address ruleset) external delegateCall(_safeDelegateModule) {}
            /**
             * @notice Binds a ruleset to a ruleset ID.
             * 
             * @dev    Throws if the `msg.sender` does not own the default list (validator admin).
             * @dev    Throws if the ruleset is not registered.
             * @dev    Throws if the ruleset ID is RULESET_ID_FIXED_OR_CUSTOM, as this is reserved for fixed rulesets that do not follow the
             *         automatic update process.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The ruleset is bound to the ruleset ID.
             *      2. A `RulesetBindingUpdated` event is emitted to indicate the ruleset binding has been updated.
             *         (If the ruleset is already bound to the ruleset ID, no event is emitted).
             *
             * @param rulesetId The ID of the ruleset to bind.
             * @param ruleset The address of the ruleset to bind.
             */
            function bindRuleset(uint8 rulesetId, address ruleset) external delegateCall(_safeDelegateModule) {}
            /*************************************************************************/
            /*                          APPLY TRANSFER POLICIES                      */
            /*************************************************************************/
            /**
             * @notice Apply the collection ruleset and options to a transfer operation of a creator token.
             *
             * @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the 
             *      _beforeTransferFrom callback.  In this case, the security policy was already applied and the operator
             *      that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
             *
             * @dev Modular rulesets with options determine the logic governing transfers.
             *
             * @dev Throws when the ruleset returns a non-zero error selector.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Transfer is allowed or denied based on the ruleset.
             *
             * @param caller      The address initiating the transfer.
             * @param from        The address of the token owner.
             * @param to          The address of the token receiver.
             */
            function validateTransfer(address caller, address from, address to) public view {
                (bytes4 errorSelector,) = _validateTransfer(AUTHORIZER_CHECK_TYPE_COLLECTION, msg.sender, caller, from, to, 0, 0);
                if (errorSelector != SELECTOR_NO_ERROR) {
                    _revertCustomErrorSelectorAsm(errorSelector);
                }
            }
            /**
             * @notice Apply the collection ruleset and options to a transfer operation of a creator token.
             *
             * @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the 
             *      _beforeTransferFrom callback.  In this case, the security policy was already applied and the operator
             *      that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
             *
             * @dev Modular rulesets with options determine the logic governing transfers.
             *
             * @dev Throws when the ruleset returns a non-zero error selector.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Transfer is allowed or denied based on the ruleset.
             *
             * @param caller      The address initiating the transfer.
             * @param from        The address of the token owner.
             * @param to          The address of the token receiver.
             * @param tokenId     The token id being transferred.
             */
            function validateTransfer(address caller, address from, address to, uint256 tokenId) public view {
                (bytes4 errorSelector,) = _validateTransfer(AUTHORIZER_CHECK_TYPE_TOKEN, msg.sender, caller, from, to, tokenId, 0);
                if (errorSelector != SELECTOR_NO_ERROR) {
                    _revertCustomErrorSelectorAsm(errorSelector);
                }
            }
            /**
             * @notice Apply the collection ruleset and options to a transfer operation of a creator token.
             *
             * @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the 
             *      _beforeTransferFrom callback.  In this case, the security policy was already applied and the operator
             *      that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
             *
             * @dev Modular rulesets with options determine the logic governing transfers.
             *
             * @dev Throws when the ruleset returns a non-zero error selector.
             * @dev Throws when authorization mode with amounts are active and the amount exceeds the authorization amount.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Transfer is allowed or denied based on the ruleset.
             *
             * @param caller      The address initiating the transfer.
             * @param from        The address of the token owner.
             * @param to          The address of the token receiver.
             * @param tokenId     The token id being transferred.
             * @param amount      The amount of the token being transferred.
             */
            function validateTransfer(address caller, address from, address to, uint256 tokenId, uint256 amount) external {
                (bytes4 errorSelector,) = _validateTransfer(AUTHORIZER_CHECK_TYPE_TOKEN_AND_AMOUNT, msg.sender, caller, from, to, tokenId, amount);
                if (errorSelector != SELECTOR_NO_ERROR) {
                    _revertCustomErrorSelectorAsm(errorSelector);
                }
                uint256 collectionAndTokenIdSlot = _getTransientOperatorSlot(msg.sender, tokenId);
                if (_getTstorish(collectionAndTokenIdSlot) != 0) {
                    uint256 amountSlot = collectionAndTokenIdSlot + 1;
                    uint256 authorizedAmount = _getTstorish(amountSlot);
                    if (amount > authorizedAmount) {
                        revert CreatorTokenTransferValidator__AmountExceedsAuthorization();
                    }
                    unchecked {
                        _setTstorish(amountSlot, authorizedAmount - amount);
                    }
                }
            }
            /**
             * @notice Apply the collection ruleset and options to a transfer operation of a creator token.
             *
             * @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the 
             *      _beforeTransferFrom callback.  In this case, the security policy was already applied and the operator
             *      that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
             *
             * @dev Modular rulesets with options determine the logic governing transfers.
             *
             * @dev Throws when the ruleset returns a non-zero error selector.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Transfer is allowed or denied based on the ruleset.
             *
             * @param caller      The address initiating the transfer.
             * @param from        The address of the token owner.
             * @param to          The address of the token receiver.
             */
            function applyCollectionTransferPolicy(address caller, address from, address to) external view {
                validateTransfer(caller, from, to);
            }
            /**
             * @notice Sets an operator for an authorized transfer that skips transfer security level
             *         validation for caller and receiver constraints.
             * 
             * @dev    An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
             *         to prevent unauthorized transfers of the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when using the wildcard operator address and the collection does not allow
             *         for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The `operator` is stored as an authorized operator for transfers.
             * 
             * @param operator  The address of the operator to set as authorized for transfers.
             * @param token     The address of the token to authorize.
             * @param tokenId   The token id to set the authorized operator for.
             */
            function beforeAuthorizedTransfer(address operator, address token, uint256 tokenId) external {
                _setOperatorInTransientStorage(operator, token, tokenId, false);
            }
            /**
             * @notice Clears the authorized operator for a token to prevent additional transfers that
             *         do not conform to the transfer security level for the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when using the wildcard operator address and the collection does not allow
             *         for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The authorized operator for the token is cleared from storage.
             * 
             * @param token     The address of the token to authorize.
             * @param tokenId   The token id to set the authorized operator for.
             */
            function afterAuthorizedTransfer(address token, uint256 tokenId) public {
                _setOperatorInTransientStorage(address(uint160(uint256(BYTES32_ZERO))), token, tokenId, false);
            }
            /**
             * @notice Sets an operator for an authorized transfer that skips transfer security level
             *         validation for caller and receiver constraints.
             * @notice This overload of `beforeAuthorizedTransfer` defaults to a tokenId of 0.
             * 
             * @dev    An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
             *         to prevent unauthorized transfers of the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when using the wildcard operator address and the collection does not allow
             *         for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The `operator` is stored as an authorized operator for transfers.
             * 
             * @param operator  The address of the operator to set as authorized for transfers.
             * @param token     The address of the token to authorize.
             */
            function beforeAuthorizedTransfer(address operator, address token) external {
                _setOperatorInTransientStorage(operator, token, 0, true);
            }
            /**
             * @notice Clears the authorized operator for a token to prevent additional transfers that
             *         do not conform to the transfer security level for the token.
             * @notice This overload of `afterAuthorizedTransfer` defaults to a tokenId of 0.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when using the wildcard operator address and the collection does not allow
             *         for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The authorized operator for the token is cleared from storage.
             * 
             * @param token     The address of the token to authorize.
             */
            function afterAuthorizedTransfer(address token) external {
                afterAuthorizedTransfer(token, 0);
            }
            /**
             * @notice Sets the wildcard operator to authorize any operator to transfer a token while
             *         skipping transfer security level validation for caller and receiver constraints.
             * 
             * @dev    An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
             *         to prevent unauthorized transfers of the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when the collection does not allow for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The wildcard operator is stored as an authorized operator for transfers.
             * 
             * @param token     The address of the token to authorize.
             * @param tokenId   The token id to set the authorized operator for.
             */
            function beforeAuthorizedTransfer(address token, uint256 tokenId) external {
                _setOperatorInTransientStorage(WILDCARD_OPERATOR_ADDRESS, token, tokenId, false);
            }
            /**
             * @notice Sets the wildcard operator to authorize any operator to transfer a token while
             *         skipping transfer security level validation for caller and receiver constraints.
             * 
             * @dev    An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
             *         to prevent unauthorized transfers of the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when the collection does not allow for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The wildcard operator is stored as an authorized operator for transfers.
             *      2. The specified amount is stored as the authorized amount.
             * 
             * @param token     The address of the token to authorize.
             * @param tokenId   The token id to set the authorized operator for.
             * @param amount    The amount of the token to authorize.
             */
            function beforeAuthorizedTransferWithAmount(address token, uint256 tokenId, uint256 amount) external {
                _setOperatorInTransientStorage(WILDCARD_OPERATOR_ADDRESS, token, tokenId, amount);
            }
            /**
             * @notice Sets the specified operator to authorize any operator to transfer a token while
             *         skipping transfer security level validation for caller and receiver constraints.
             * 
             * @dev    An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
             *         to prevent unauthorized transfers of the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when the collection does not allow for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The specified operator is stored as an authorized operator for transfers.
             *      2. The specified amount is stored as the authorized amount for the operator.
             * 
             * @param operator  The address of the operator to set as authorized for transfers.
             * @param token     The address of the token to authorize.
             * @param tokenId   The token id to set the authorized operator for.
             * @param amount    The amount of the token to authorize.
             */
            function beforeAuthorizedTransferWithAmount(address operator, address token, uint256 tokenId, uint256 amount) external {
                _setOperatorInTransientStorage(operator, token, tokenId, amount);
            }
            /**
             * @notice Clears the authorized operator for a token to prevent additional transfers that
             *         do not conform to the transfer security level for the token.
             * 
             * @dev    Throws when authorization mode is disabled for the collection.
             * @dev    Throws when using the wildcard operator address and the collection does not allow
             *         for wildcard authorized operators.
             * @dev    Throws when the caller is not an allowed authorizer for the collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The authorized operator for the token is cleared from storage.
             * 
             * @param token     The address of the token to authorize.
             * @param tokenId   The token id to set the authorized operator for.
             */
            function afterAuthorizedTransferWithAmount(address token, uint256 tokenId) external {
                _setOperatorInTransientStorage(address(uint160(uint256(BYTES32_ZERO))), token, tokenId, 0);
            }
            /*************************************************************************/
            /*                               SIMULATION                              */
            /*************************************************************************/
            /**
             * @notice Simulates transfer validation of a collection with its current ruleset and options.
             * 
             * @dev    Authorization mode overrides are not taken into account.
             *
             * @param collection The address of the collection.
             * @param caller     The address initiating the transfer.
             * @param from       The address of the token owner.
             * @param to         The address of the token receiver.
             * @return isTransferAllowed True if the transfer is allowed, false otherwise.
             * @return errorCode         The error code if the transfer is not allowed.
             */
            function validateTransferSim(
                address collection, 
                address caller, 
                address from, 
                address to
            ) external view returns (bool isTransferAllowed, bytes4 errorCode) {
                (errorCode,) = _validateTransfer(AUTHORIZER_CHECK_TYPE_COLLECTION, collection, caller, from, to, 0, 0);
                isTransferAllowed = errorCode == SELECTOR_NO_ERROR;
            }
            /**
             * @notice Simulates transfer validation of a collection with its current ruleset and options.
             *
             * @dev    Authorization mode overrides are not taken into account.
             *
             * @param collection The address of the collection.
             * @param caller     The address initiating the transfer.
             * @param from       The address of the token owner.
             * @param to         The address of the token receiver.
             * @param tokenId    The token id being transferred.
             * @return isTransferAllowed True if the transfer is allowed, false otherwise.
             * @return errorCode         The error code if the transfer is not allowed.
             */
            function validateTransferSim(
                address collection,
                address caller, 
                address from, 
                address to, 
                uint256 tokenId
            ) external view returns (bool isTransferAllowed, bytes4 errorCode) {
                (errorCode,) = _validateTransfer(AUTHORIZER_CHECK_TYPE_TOKEN, collection, caller, from, to, tokenId, 0);
                isTransferAllowed = errorCode == SELECTOR_NO_ERROR;
            }
            /**
             * @notice Simulates transfer validation of a collection with its current ruleset and options.
             *
             * @dev    Authorization mode overrides are not taken into account.
             *
             * @param collection The address of the collection.
             * @param caller     The address initiating the transfer.
             * @param from       The address of the token owner.
             * @param to         The address of the token receiver.
             * @param tokenId    The token id being transferred.
             * @param amount     The amount of the token being transferred.
             * @return isTransferAllowed True if the transfer is allowed, false otherwise.
             * @return errorCode         The error code if the transfer is not allowed.     
             */
            function validateTransferSim(
                address collection,
                address caller, 
                address from, 
                address to, 
                uint256 tokenId, 
                uint256 amount
            ) external view returns (bool isTransferAllowed, bytes4 errorCode) {
                (errorCode,) = _validateTransfer(AUTHORIZER_CHECK_TYPE_TOKEN_AND_AMOUNT, collection, caller, from, to, tokenId, amount);
                isTransferAllowed = errorCode == SELECTOR_NO_ERROR;
            }
            /*************************************************************************/
            /*                    COLLECTION AND LIST MANAGEMENT                     */
            /*************************************************************************/
            /**
             * @notice Creates a new list id.  The list id is a handle to allow editing of blacklisted and whitelisted accounts
             *         and codehashes.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. A new list with the specified name is created.
             *      2. The caller is set as the owner of the new list.
             *      3. A `CreatedList` event is emitted.
             *      4. A `ReassignedListOwnership` event is emitted.
             *
             * @param  name The name of the new list.
             * @return id   The id of the new list.
             */
            function createList(string calldata name) public delegateCall(_managementModule) returns (uint48 id) {}
            /**
             * @notice Creates a new list id, and copies all blacklisted, whitelisted, and authorizer accounts and codehashes 
             *         from the specified source list.
             *
             * @dev    <h4>Postconditions:</h4>
             *         1. A new list with the specified name is created.
             *         2. The caller is set as the owner of the new list.
             *         3. A `CreatedList` event is emitted.
             *         4. A `ReassignedListOwnership` event is emitted.
             *         5. All blacklisted and whitelisted accounts and codehashes from the specified source list are copied
             *            to the new list.
             *         6. An `AddedAccountToList` event is emitted for each blacklisted and whitelisted account copied.
             *         7. An `AddedCodeHashToList` event is emitted for each blacklisted and whitelisted codehash copied.
             *
             * @param  name         The name of the new list.
             * @param  sourceListId The id of the source list to copy from.
             * @return id           The id of the new list.
             */
            function createListCopy(string calldata name, uint48 sourceListId) external delegateCall(_managementModule) returns (uint48 id) {}
            /**
             * @notice Creates a new list id, and copies all accounts and codehashes from the
             *         specified source list for each specified list type.
             *
             * @dev    <h4>Postconditions:</h4>
             *         1. A new list with the specified name is created.
             *         2. The caller is set as the owner of the new list.
             *         3. A `CreatedList` event is emitted.
             *         4. A `ReassignedListOwnership` event is emitted.
             *         5. All accounts and codehashes from the specified source list / list types are copied
             *            to the new list.
             *         6. An `AddedAccountToList` event is emitted for each account copied.
             *         7. An `AddedCodeHashToList` event is emitted for each codehash copied.
             *
             * @param  name         The name of the new list.
             * @param  sourceListId The id of the source list to copy from.
             * @param  listTypes    The list types to copy from the source list.
             * @return id           The id of the new list.
             */
            function createListCopy(string calldata name, uint48 sourceListId, uint8[] calldata listTypes) external delegateCall(_managementModule) returns (uint48 id) {}
            /**
             * @notice Transfer ownership of a list to a new owner.
             *
             * @dev Throws when the new owner is the zero address.
             * @dev Throws when the caller does not own the specified list.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The list ownership is transferred to the new owner.
             *      2. A `ReassignedListOwnership` event is emitted.
             *
             * @param id       The id of the list.
             * @param newOwner The address of the new owner.
             */
            function reassignOwnershipOfList(uint48 id, address newOwner) public delegateCall(_managementModule) {}
            /**
             * @notice Renounce the ownership of a list, rendering the list immutable.
             *
             * @dev Throws when the caller does not own the specified list.
             * @dev Throws when list id is zero (default list).
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The ownership of the specified list is renounced.
             *      2. A `ReassignedListOwnership` event is emitted.
             *
             * @param id The id of the list.
             */
            function renounceOwnershipOfList(uint48 id) public delegateCall(_managementModule) {}
            /**
             * @notice Set the ruleset id, global / ruleset options, fixed / custom ruleset for a collection.
             *
             * @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
             * @dev Throws when setting a custom ruleset to an unregistered ruleset address.
             * @dev Throws when setting a ruleset id that is not bound to a ruleset address.
             * @dev Throws when setting a custom ruleset with a managed ruleset id.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The ruleset of the specified collection is set to the new value.
             *      2. Global options and ruleset-specific options of the specified collection are set to the new value.
             *      3. A `SetCollectionRuleset` event is emitted.
             *      4. A `SetCollectionSecurityPolicyOptions` event is emitted.
             *
             * @param collection                 The address of the collection.
             * @param rulesetId                  The new ruleset id to apply.
             * @param customRuleset              The address of the custom ruleset to apply. Must be address(0) unless ruleset
             *                                   id is RULESET_ID_FIXED_OR_CUSTOM (255).
             * @param globalOptions              The global options to apply.
             * @param rulesetOptions             The ruleset-specific options to apply.
             */
            function setRulesetOfCollection(
                address collection, 
                uint8 rulesetId,
                address customRuleset,
                uint8 globalOptions,
                uint16 rulesetOptions
            ) external delegateCall(_managementModule) {}
            /**
             * @notice Set expansion words and datums for a collection.
             *
             * @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Included expansion words and datums are set for the specified collection.
             *      2. A `SetCollectionExpansionWords` event is emitted for each expansion word included in the request.
             *      3. A `SetCollectionExpansionDatums` event is emitted for each expansion datum included in the request.
             *
             * @param collection                 The address of the collection.
             * @param expansionWords             The expansion words to set.
             * @param expansionDatums            The expansion datums to set.
             */
            function setExpansionSettingsOfCollection(
                address collection, 
                ExpansionWord[] calldata expansionWords,
                ExpansionDatum[] calldata expansionDatums
            ) external delegateCall(_managementModule) {}
            /**
             * @notice Set the token type setting of a collection.
             *
             * @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The token type of the specified collection is set to the new value.
             *      2. A `SetTokenType` event is emitted.
             *
             * @param collection  The address of the collection.
             * @param tokenType   The new transfer security level to apply.
             */
            function setTokenTypeOfCollection(
                address collection, 
                uint16 tokenType
            ) external delegateCall(_managementModule) {}
            /**
             * @notice Applies the specified list to a collection.
             * 
             * @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
             * @dev Throws when the specified list id does not exist.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The list of the specified collection is set to the new value.
             *      2. An `AppliedListToCollection` event is emitted.
             *
             * @param collection The address of the collection.
             * @param id         The id of the operator whitelist.
             */
            function applyListToCollection(address collection, uint48 id) public delegateCall(_managementModule) {}
            /**
             * @notice Adds accounts to the frozen accounts list of a collection.
             * 
             * @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The accounts are added to the list of frozen accounts for a collection.
             *      2. A `AccountFrozenForCollection` event is emitted for each account added to the list.
             *
             * @param collection        The address of the collection.
             * @param accountsToFreeze  The list of accounts to added to frozen accounts.
             */
            function freezeAccountsForCollection(address collection, address[] calldata accountsToFreeze) external delegateCall(_managementModule) {}
            /**
             * @notice Removes accounts to the frozen accounts list of a collection.
             * 
             * @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. The accounts are removed from the list of frozen accounts for a collection.
             *      2. A `AccountUnfrozenForCollection` event is emitted for each account removed from the list.
             *
             * @param collection          The address of the collection.
             * @param accountsToUnfreeze  The list of accounts to remove from frozen accounts.
             */
            function unfreezeAccountsForCollection(address collection, address[] calldata accountsToUnfreeze) external delegateCall(_managementModule) {}
            /**
             * @notice Adds one or more accounts to a list of specified list type.
             *
             * @dev Throws when the caller does not own the specified list.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Accounts not previously in the list are added.
             *      2. An `AddedAccountToList` event is emitted for each account that is newly added to the list.
             *
             * @param id       The id of the list.
             * @param listType The type of the list.
             * @param accounts The addresses of the accounts to add.
             */
            function addAccountsToList(
                uint48 id,
                uint8 listType,
                address[] calldata accounts
            ) external delegateCall(_managementModule) {}
            /**
             * @notice Removes one or more accounts from a list of the specified list type.
             *
             * @dev Throws when the caller does not own the specified list.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Accounts previously in the list are removed.
             *      2. A `RemovedAccountFromList` event is emitted for each account that is removed from the list.
             *
             * @param id       The id of the list.
             * @param listType The type of the list.
             * @param accounts The addresses of the accounts to remove.
             */
            function removeAccountsFromList(
                uint48 id,
                uint8 listType,
                address[] calldata accounts
            ) external delegateCall(_managementModule) {}
            /**
             * @notice Adds one or more codehashes to a list of specified list type.
             *
             * @dev Throws when the caller does not own the specified list.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Codehashes not previously in the list are added.
             *      2. An `AddedCodeHashToList` event is emitted for each codehash that is newly added to the list.
             *
             * @param id         The id of the list.
             * @param listType   The type of the list.
             * @param codehashes The codehashes to add.
             */
            function addCodeHashesToList(
                uint48 id,
                uint8 listType,
                bytes32[] calldata codehashes
            ) external delegateCall(_managementModule) {}
            /**
             * @notice Removes one or more codehashes from a list of the specified list type.
             *
             * @dev Throws when the caller does not own the specified list.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Codehashes previously in the list are removed.
             *      2. A `RemovedCodeHashFromList` event is emitted for each codehash that is removed from the list.
             *
             * @param id         The id of the list.
             * @param listType   The type of the list.
             * @param codehashes The codehashes to remove.
             */
            function removeCodeHashesFromList(
                uint48 id,
                uint8 listType,
                bytes32[] calldata codehashes
            ) external delegateCall(_managementModule) {}
            /*************************************************************************/
            /*                      RULESET VIEW ACCESSORS                           */
            /*************************************************************************/
            /**
             * @notice Returns true if the specified ruleset is registered, false otherwise.
             */
            function isRulesetRegistered(address ruleset) external view returns (bool) {
                return validatorStorage().registeredRulesets[ruleset];
            }
            /**
             * @notice Returns the address of the ruleset currently bound to the specified ruleset id.
             */
            function boundRuleset(uint8 rulesetId) external view returns (address) {
                if (rulesetId == RULESET_ID_FIXED_OR_CUSTOM) {
                    return address(0);
                }
                return validatorStorage().rulesetBindings[rulesetId];
            }
            /*************************************************************************/
            /*           COLLECTION AND LIST SETTINGS VIEW ACCESSORS                 */
            /*************************************************************************/
            /**
             * @notice Returns the id of the list that was created.
             */
            function lastListId() external view returns (uint48) {
                return validatorStorage().lastListId;
            }
            /**
             * @notice Returns the owner of the specified list id.
             */
            function listOwners(uint48 id) external view returns (address) {
                return validatorStorage().listOwners[id];
            }
            /**
             * @notice Get the security policy of the specified collection.
             * @param collection The address of the collection.
             * @return           The security policy of the specified collection, which includes:
             *                   Ruleset id, list id, global options, ruleset-specific options, optional custom ruleset address,
             *                   and token type (if registered).
             */
            function getCollectionSecurityPolicy(
                address collection
            ) external view returns (CollectionSecurityPolicy memory) {
                return validatorStorage().collectionSecurityPolicies[collection];
            }
            /**
             * @notice Returns expansion word settings for a collection.
             *
             * @param  tokenAddress     The smart contract address of the token.
             * @param  keys             The expansion word keys to retrieve.
             */
            function getCollectionExpansionWords(
                address tokenAddress, 
                bytes32[] calldata keys
            ) public view returns (bytes32[] memory values) {
                if(keys.length > 0) {
                    mapping (bytes32 => bytes32) storage ptrExpansionWordsForCollection = 
                        validatorStorage().collectionExpansionWords[tokenAddress];
                    values = new bytes32[](keys.length);
                    for (uint256 i = 0; i < keys.length; ++i) {
                        values[i] = ptrExpansionWordsForCollection[keys[i]];
                    }
                }
            }
            /**
             * @notice Returns expansion datum settings for a collection.
             *
             * @param  tokenAddress     The smart contract address of the token.
             * @param  keys             The expansion datum keys to retrieve.
             */
            function getCollectionExpansionDatums(
                address tokenAddress, 
                bytes32[] calldata keys
            ) public view returns (bytes[] memory values) {
                if(keys.length > 0) {
                    mapping (bytes32 => bytes) storage ptrExpansionDatumsForCollection = 
                        validatorStorage().collectionExpansionDatums[tokenAddress];
                    values = new bytes[](keys.length);
                    for (uint256 i = 0; i < keys.length; ++i) {
                        values[i] = ptrExpansionDatumsForCollection[keys[i]];
                    }
                }
            }
            /**
             * @notice Get accounts by list id and list type.
             * @param  id The id of the list.
             * @param  listType The type of the list.
             * @return An array of accounts in the list of the specified type.
             */
            function getListAccounts(uint48 id, uint8 listType) public view returns (address[] memory) {
                return validatorStorage().lists[listType][id].enumerableAccounts.values();
            }
            /**
             * @notice Get codehashes by list id and list type.
             * @param  id The id of the list.
             * @param  listType The type of the list.
             * @return An array of codehashes in the list of the specified type.
             */
            function getListCodeHashes(uint48 id, uint8 listType) public view returns (bytes32[] memory) {
                return validatorStorage().lists[listType][id].enumerableCodehashes.values();
            }
            /**
             * @notice Check if an account is found in a specified list id / list type.
             * @param id       The id of the list.
             * @param listType The type of the list.
             * @param account  The address of the account to check.
             * @return         True if the account is in the specified list / type, false otherwise.
             */
            function isAccountInList(uint48 id, uint8 listType, address account) public view returns (bool) {
                return validatorStorage().lists[listType][id].nonEnumerableAccounts[account];
            }
            /**
             * @notice Check if a codehash is in a specified list / type.
             * @param id       The id of the list.
             * @param listType The type of the list.
             * @param codehash  The codehash to check.
             * @return         True if the codehash is in the specified list / type, false otherwise.
             */
            function isCodeHashInList(uint48 id, uint8 listType, bytes32 codehash) public view returns (bool) {
                return validatorStorage().lists[listType][id].nonEnumerableCodehashes[codehash];
            }
            /**
             * @notice Get accounts in list by collection and list type.
             * @param collection The address of the collection.
             * @param listType   The type of the list.
             * @return           An array of accounts.
             */
            function getListAccountsByCollection(address collection, uint8 listType) external view returns (address[] memory) {
                return getListAccounts(validatorStorage().collectionSecurityPolicies[collection].listId, listType);
            }
            /**
             * @notice Get codehashes in list by collection and list type.
             * @param collection The address of the collection.
             * @param listType   The type of the list.
             * @return           An array of codehashes.
             */
            function getListCodeHashesByCollection(address collection, uint8 listType) external view returns (bytes32[] memory) {
                return getListCodeHashes(validatorStorage().collectionSecurityPolicies[collection].listId, listType);
            }
            /**
             * @notice Get frozen accounts by collection.
             * @param collection The address of the collection.
             * @return           An array of frozen accounts.
             */
            function getFrozenAccountsByCollection(address collection) external view returns (address[] memory) {
                return validatorStorage().frozenAccounts[collection].enumerableAccounts.values();
            }
            /**
             * @notice Check if an account is in the list by a specified collection and list type.
             * @param collection The address of the collection.
             * @param listType   The type of the list.
             * @param account    The address of the account to check.
             * @return           True if the account is in the list / list type of the specified collection, false otherwise.
             */
            function isAccountInListByCollection(address collection, uint8 listType, address account) external view returns (bool) {
                return isAccountInList(validatorStorage().collectionSecurityPolicies[collection].listId, listType, account);
            }
            /**
             * @notice Check if a codehash is in the list by a specified collection / list type.
             * @param collection The address of the collection.
             * @param listType   The type of the list.
             * @param codehash   The codehash to check.
             * @return           True if the codehash is in the list / list type of the specified collection, false otherwise.
             */
            function isCodeHashInListByCollection(address collection, uint8 listType, bytes32 codehash) external view returns (bool) {
                return isCodeHashInList(validatorStorage().collectionSecurityPolicies[collection].listId, listType, codehash);
            }
            /**
             * @notice Check if an account is frozen for a specified collection.
             * @param collection The address of the collection.
             * @param account    The address of the account to check.
             * @return           True if the account is frozen by the specified collection, false otherwise.
             */
            function isAccountFrozenForCollection(address collection, address account) external view returns (bool) {
                return validatorStorage().frozenAccounts[collection].nonEnumerableAccounts[account];
            }
            /**
             * @notice Returns true if the specified account has verified a signature on the registry, false otherwise.
             */
            function isVerifiedEOA(address account) public view returns (bool) {
                return IEOARegistry(_eoaRegistry).isVerifiedEOA(account);
            }
            /**
             * @notice Makes a delegatecall to the validator ruleset module that perform the transfer validation logic.
             *
             * @dev Throws when the `msg.sender` is not equal to address(this).  This guarantees that staticcall context
             *      is properly preserved.
             */
            function validateTransferDelegateCall(
                uint256 authorizerCheckType,
                address collection,
                address caller, 
                address from, 
                address to,
                uint256 tokenId,
                uint256 amount) external /*view*/ returns (bytes4 errorSelector, uint16 tokenType) { 
                if (msg.sender != address(this)) {
                    revert CreatorTokenTransferValidator__OnlyValidatorCanAccessThisFunction();
                }
                address validatorModuleAddress;
                (
                    validatorModuleAddress,
                    tokenType
                ) = _getCollectionTokenTypeAndValidatorModule(collection);
                errorSelector = _delegateCallRuleset(
                    validatorModuleAddress,
                    authorizerCheckType,
                    collection,
                    caller,
                    from,
                    to,
                    tokenId,
                    amount
                );
            }
            /**
             * @notice ERC-165 Interface Support
             * @dev    Do not remove LEGACY from this contract or future contracts.  
             *         Doing so will break backwards compatibility with V1 and V2 creator tokens.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
                return
                    interfaceId == LEGACY_TRANSFER_VALIDATOR_INTERFACE_ID ||
                    interfaceId == type(ITransferValidator).interfaceId ||
                    interfaceId == type(IPermitC).interfaceId ||
                    interfaceId == type(IEOARegistry).interfaceId ||
                    super.supportsInterface(interfaceId);
            }
            /*************************************************************************/
            /*                                HELPERS                                */
            /*************************************************************************/
            /**
             * @dev Hook that is called before any permitted token transfer that goes through Permit-C.
             *      Applies the collection transfer policy, using the operator that called Permit-C as the caller.
             *      This allows creator token standard protections to extend to permitted transfers.
             * 
             * @param token  The collection address of the token being transferred.
             * @param from   The address of the token owner.
             * @param to     The address of the token receiver.
             * @param id     The token id being transferred.
             */
            function _beforeTransferFrom(
                uint256 tokenType,
                address token, 
                address from, 
                address to, 
                uint256 id, 
                uint256 amount
            ) internal override returns (bool isError) {
                (bytes4 selector, uint16 collectionTokenType) = 
                    _validateTransfer(
                        tokenType == TOKEN_TYPE_ERC721 ? AUTHORIZER_CHECK_TYPE_TOKEN : AUTHORIZER_CHECK_TYPE_TOKEN_AND_AMOUNT, 
                        token, 
                        msg.sender, 
                        from, 
                        to, 
                        id, 
                        amount
                    );
                if (collectionTokenType == DEFAULT_TOKEN_TYPE || collectionTokenType == tokenType) {
                    isError = SELECTOR_NO_ERROR != selector;
                } else {
                    revert CreatorTokenTransferValidator__TokenTypesDoNotMatch();
                }
            }
            /**
             * @notice Apply the collection ruleset and options to a transfer operation of a creator token.
             *
             * @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the 
             *      _beforeTransferFrom callback.  In this case, the security policy was already applied and the operator
             *      that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
             *
             * @dev Modular rulesets with options determine the logic governing transfers.
             *
             * @dev Throws when the ruleset returns a non-zero error selector.
             *
             * @dev <h4>Postconditions:</h4>
             *      1. Transfer is allowed or denied based on the ruleset.
             *
             * @param collection  The collection address of the token being transferred.
             * @param caller      The address initiating the transfer.
             * @param from        The address of the token owner.
             * @param to          The address of the token receiver.
             * @param tokenId     The token id being transferred.
             * 
             * @return The selector value for an error if the transfer is not allowed, `SELECTOR_NO_ERROR` if the transfer is allowed.
             */
            function _validateTransfer(
                uint256 authorizerCheckType,
                address collection, 
                address caller, 
                address from, 
                address to,
                uint256 tokenId,
                uint256 amount
            ) internal view returns (bytes4,uint16) {
                if (caller == address(this)) { 
                    // If the caller is self (Permit-C Processor) it means we have already applied operator validation in the 
                    // _beforeTransferFrom callback.  In this case, the security policy was already applied and the operator
                    // that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
                    return (SELECTOR_NO_ERROR, DEFAULT_TOKEN_TYPE);
                }
                return IRulesetDelegateCall(address(this)).validateTransferDelegateCall(
                    authorizerCheckType,
                    collection,
                    caller,
                    from,
                    to,
                    tokenId,
                    amount
                );
            }
            /**
             * @dev Internal function used to get the collection token type and validator module address.
             *
             * @param collection The collection address to get the token type and validator module address for.
             * @return validatorModuleAddress  The validator module address afor the collection.
             * @return tokenType               The token type for the collection.
             */
            function _getCollectionTokenTypeAndValidatorModule(
                address collection
            ) internal view returns (address validatorModuleAddress, uint16 tokenType) {
                CollectionSecurityPolicy storage ptrSecurityPolicy = validatorStorage().collectionSecurityPolicies[collection];
                validatorModuleAddress = 
                    ptrSecurityPolicy.rulesetId < RULESET_ID_FIXED_OR_CUSTOM ? 
                    validatorStorage().rulesetBindings[ptrSecurityPolicy.rulesetId] : 
                    ptrSecurityPolicy.customRuleset;
                tokenType = ptrSecurityPolicy.tokenType;
            }
            /**
             * @dev Internal function used to efficiently revert with a custom error selector.
             *
             * @param errorSelector The error selector to revert with.
             */
            function _revertCustomErrorSelectorAsm(bytes4 errorSelector) internal pure {
                assembly {
                    mstore(0x00, errorSelector)
                    revert(0x00, 0x04)
                }
            }
            /**
             * @dev Internal function used to check if authorization mode can be activated for a transfer.
             * 
             * @dev Throws when the collection has not enabled authorization mode.
             * @dev Throws when the wildcard operator is being set for a collection that does not
             *      allow wildcard operators.
             * @dev Throws when the authorizer is not in the list of approved authorizers for
             *      the collection.
             * 
             * @param collection  The collection address to activate authorization mode for a transfer.
             * @param operator    The operator specified by the authorizer to allow transfers.
             * @param authorizer  The address of the authorizer making the call.
             */
            function _checkCollectionAllowsAuthorizerAndOperator(
                address collection, 
                address operator, 
                address authorizer
            ) internal view {
                CollectionSecurityPolicy storage collectionSecurityPolicy = validatorStorage().collectionSecurityPolicies[collection];
                if (_isFlagSet(collectionSecurityPolicy.globalOptions, FLAG_GLOBAL_DISABLE_AUTHORIZATION_MODE)) {
                    revert CreatorTokenTransferValidator__AuthorizationDisabledForCollection();
                }
                if (_isFlagSet(collectionSecurityPolicy.globalOptions, FLAG_GLOBAL_AUTHORIZERS_CANNOT_SET_WILDCARD_OPERATORS)) {
                    if (operator == WILDCARD_OPERATOR_ADDRESS) {
                        revert CreatorTokenTransferValidator__WildcardOperatorsCannotBeAuthorizedForCollection();
                    }
                }
                if (!validatorStorage().lists[LIST_TYPE_AUTHORIZERS][collectionSecurityPolicy.listId].nonEnumerableAccounts[authorizer]) {
                    if (!_isFlagSet(collectionSecurityPolicy.globalOptions, FLAG_GLOBAL_CUSTOM_LIST_SUPPLEMENTS_DEFAULT_LIST) || 
                        !validatorStorage().lists[LIST_TYPE_AUTHORIZERS][DEFAULT_LIST_ID].nonEnumerableAccounts[authorizer]) {
                        revert CreatorTokenTransferValidator__CallerMustBeAnAuthorizer();
                    }
                }
            }
            /**
             * @dev Modifier to apply the allowed authorizer and operator for collection checks.
             * 
             * @dev Throws when the collection has not enabled authorization mode.
             * @dev Throws when the wildcard operator is being set for a collection that does not
             *      allow wildcard operators.
             * @dev Throws when the authorizer is not in the list of approved authorizers for
             *      the collection.
             * 
             * @param collection  The collection address to activate authorization mode for a transfer.
             * @param operator    The operator specified by the authorizer to allow transfers.
             * @param authorizer  The address of the authorizer making the call.
             */
            modifier whenAuthorizerAndOperatorEnabledForCollection(
                address collection, 
                address operator, 
                address authorizer
            ) {
                _checkCollectionAllowsAuthorizerAndOperator(collection, operator, authorizer);
                _;
            }
            /**
             * @dev Internal function for setting the authorized operator in storage for a token and collection.
             * 
             * @param operator         The allowed operator for an authorized transfer.
             * @param collection       The address of the collection that the operator is authorized for.
             * @param tokenId          The id of the token that is authorized.
             * @param allowAnyTokenId  Flag if the authorizer is enabling transfers for any token id
             */
            function _setOperatorInTransientStorage(
                address operator,
                address collection, 
                uint256 tokenId,
                bool allowAnyTokenId
            ) internal whenAuthorizerAndOperatorEnabledForCollection(collection, operator, msg.sender) {
                _setTstorish(_getTransientOperatorSlot(collection), (allowAnyTokenId ? 1 << 255 : 0) | uint256(uint160(operator)));
                _setTstorish(_getTransientOperatorSlot(collection, tokenId), uint256(uint160(operator)));
            }
            /**
             * @dev Internal function for setting the authorized operator and amount in storage for a token and collection.
             * 
             * @param operator    The allowed operator for an authorized transfer.
             * @param collection  The address of the collection that the operator is authorized for.
             * @param tokenId     The id of the token that is authorized.
             * @param amount      The amount of the token that is authorized.
             */
            function _setOperatorInTransientStorage(
                address operator,
                address collection, 
                uint256 tokenId,
                uint256 amount
            ) internal whenAuthorizerAndOperatorEnabledForCollection(collection, operator, msg.sender) {
                _setTstorish(_getTransientOperatorSlot(collection), uint256(uint160(operator)));
                uint256 collectionAndTokenIdSlot = _getTransientOperatorSlot(collection, tokenId);
                _setTstorish(collectionAndTokenIdSlot, uint256(uint160(operator)));
                _setTstorish(collectionAndTokenIdSlot + 1, amount);
            }
            
            /**
             * @dev Internal function triggered when the Tstore support is activated.
             */
            function _onTstoreSupportActivated() internal virtual override {
                // Nothing to do here
            }
            /**
             * @dev Internal function used to delegate call the ruleset module for transfer validation and return results.
             */
            function _delegateCallRuleset(
                address _ruleSet,
                uint256 _authorizerCheckType,
                address _collection,
                address _caller, 
                address _from, 
                address _to,
                uint256 _tokenId,
                uint256 _amount
            ) internal returns (bytes4 errorSelector) {
                bytes4 selector = IRuleset.validateTransfer.selector;
                assembly {
                    let ptr := mload(0x40)
                    mstore(ptr, selector)
                    mstore(add(ptr,0x04), _authorizerCheckType)
                    mstore(add(ptr,0x24), _collection)
                    mstore(add(ptr,0x44), _caller)
                    mstore(add(ptr,0x64), _from)
                    mstore(add(ptr,0x84), _to)
                    mstore(add(ptr,0xA4), _tokenId)
                    mstore(add(ptr,0xC4), _amount)
                    mstore(0x40, add(ptr,0xE4))
                    let result := delegatecall(gas(), _ruleSet, ptr, 0xE4, 0x00, 0x20)
                    if iszero(result) {
                        // Call has failed, retrieve the error message and revert
                        let size := returndatasize()
                        returndatacopy(0, 0, size)
                        revert(0, size)
                    }
                    errorSelector := mload(0x00)
                }
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "@limitbreak/tm-core-lib/src/utils/structs/EnumerableSet.sol";
        /**
         * @dev This struct contains the security policy settings for a collection.
         */
        struct CollectionSecurityPolicy {
            uint8 rulesetId;
            uint48 listId;
            address customRuleset;
            uint8 globalOptions;
            uint16 rulesetOptions;
            uint16 tokenType;
        }
        /**
         * @dev This struct is internally for the storage of account and codehash lists.
         */
        struct List {
            EnumerableSet.AddressSet enumerableAccounts;
            EnumerableSet.Bytes32Set enumerableCodehashes;
            mapping (address => bool) nonEnumerableAccounts;
            mapping (bytes32 => bool) nonEnumerableCodehashes;
        }
        /**
         * @dev This struct is internally for the storage of account lists.
         */
        struct AccountList {
            EnumerableSet.AddressSet enumerableAccounts;
            mapping (address => bool) nonEnumerableAccounts;
        }
        /**
         * @dev This struct contains a key and value pair for future expansion data words that are 32 bytes long.
         */
        struct ExpansionWord {
            bytes32 key;
            bytes32 value;
        }
        /**
         * @dev This struct contains a key and value pair for future expansion data bytes that are variable length.
         */
        struct ExpansionDatum {
            bytes32 key;
            bytes value;
        }
        /**
         * @dev This struct contains the storage layout for the validator contract, excluding Permit-C and Tstorish data.
         */
        struct ValidatorStorage {
            /// @notice Keeps track of the most recently created list id.
            uint48 lastListId;
            /// @dev Used as a collision guard.
            mapping (address => address) transientOperator;
            /// @notice Mapping of list ids to list owners
            mapping (uint48 => address) listOwners;
            /// @dev Mapping of collection addresses to their security policy settings
            mapping (address => CollectionSecurityPolicy) collectionSecurityPolicies;
            /// @dev Mapping of collections to accounts that are frozen for those collections
            mapping (address => AccountList) frozenAccounts;
            /// @dev Mapping of list ids to list data
            mapping (uint8 => mapping (uint48 => List)) lists;
            /// @dev Mapping of collection addressses to any future expansion data words settings that may be used in the future
            mapping (address collection => mapping (bytes32 extension => bytes32 word)) collectionExpansionWords;
            /// @dev Mapping of collection addressses to any future expansion data bytes settings that may be used in the future
            mapping (address collection => mapping (bytes32 extension => bytes data)) collectionExpansionDatums;
            /// @dev Mapping of addresses to a boolean indicating if they are registered trusted validator modules
            mapping (address => bool) registeredRulesets;
            /// @dev Mapping of security levels to their current implementation modules
            mapping (uint8 => address) rulesetBindings;
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /// @dev Thrown when admin attempts to bind a ruleset to the fixed/custom ruleset ID.
        error CreatorTokenTransferValidator__AdminCannotAssignRulesetToRulesetIdCustom();
        /// @dev Thrown when validating transfers with amount if authorization by amount mode is active and the amount
        ///      exceeds the pre-authorized amount.
        error CreatorTokenTransferValidator__AmountExceedsAuthorization();
        /// @dev Thrown when attempting to set a authorized operator when authorization mode is disabled.
        error CreatorTokenTransferValidator__AuthorizationDisabledForCollection();
        /// @dev Thrown when attempting to call a function that requires the caller to be the list owner.
        error CreatorTokenTransferValidator__CallerDoesNotOwnList();
        /// @dev Thrown when authorizing a transfer for a collection using authorizers and the msg.sender is not in the 
        ///      authorizer list.
        error CreatorTokenTransferValidator__CallerMustBeAnAuthorizer();
        /// @dev Thrown when validating a transfer for a collection using whitelists and the operator is not on the whitelist.
        error CreatorTokenTransferValidator__CallerMustBeWhitelisted();
        /// @dev Thrown when attempting to call a function that requires owner or default admin role for a collection that the 
        ///      caller does not have.
        error CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
        /// @dev Thrown when validating a transfer for a collection using whitelists and the operator or from account is not on the whitelist.
        error CreatorTokenTransferValidator__CallerOrFromMustBeWhitelisted();
        /// @dev Thrown when attempting to renounce ownership of the default list id.
        error CreatorTokenTransferValidator__CannotRenounceOwnershipOfDefaultList();
        /// @dev Thrown when setting the ruleset for a collection when a reserved ruleset id is used, but a custom ruleset
        ///      is specified.
        error CreatorTokenTransferValidator__CannotSetCustomRulesetOnManagedRulesetId();
        /// @dev Thrown when constructor args are not valid
        error CreatorTokenTransferValidator__InvalidConstructorArgs();
        /// @dev Thrown when setting the transfer security level to an invalid value.
        error CreatorTokenTransferValidator__InvalidTransferSecurityLevel();
        /// @dev Thrown when attempting to set a list id that does not exist.
        error CreatorTokenTransferValidator__ListDoesNotExist();
        /// @dev Thrown when attempting to transfer the ownership of a list to the zero address.
        error CreatorTokenTransferValidator__ListOwnershipCannotBeTransferredToZeroAddress();
        /// @dev Thrown when attempting to call the transfer validation logic externally, as staticcall guarantees are needed.
        error CreatorTokenTransferValidator__OnlyValidatorCanAccessThisFunction();
        /// @dev Thrown when validating a transfer for a collection using blacklists and the operator is on the blacklist.
        error CreatorTokenTransferValidator__OperatorIsBlacklisted();
        /// @dev Thrown when validating an OTC transfer with EIP-7702 Delegation when disabled by security settings.
        error CreatorTokenTransferValidator__OTCNotAllowedFor7702Delegates();
        /// @dev Thrown when validating an OTC transfer from Smart Wallets when disabled by security settings.
        error CreatorTokenTransferValidator__OTCNotAllowedForSmartWallets();
        /// @dev Thrown when a frozen account is the receiver of a transfer
        error CreatorTokenTransferValidator__ReceiverAccountIsFrozen();
        /// @dev Thrown when validating a transfer for a collection that does not allow receiver to have code and the receiver 
        ///      has code.
        error CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode();
        /// @dev Thrown when validating a transfer for a collection that requires receivers be verified EOAs and the receiver 
        ///      is not verified.
        error CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified();
        /// @dev Thrown when attempting to register a ruleset that is not a contract.
        error CreatorTokenTransferValidator__RulesetIsNotContract();
        /// @dev Thrown when attempting to register a ruleset that is not pure.
        error CreatorTokenTransferValidator__RulesetIsNotPure();
        /// @dev Thrown when admin attempts to bind a ruleset that is not registered, or a collection admin tries to 
        ///      set their fixed/custom ruleset to an unregistered ruleset.
        error CreatorTokenTransferValidator__RulesetIsNotRegistered();
        /// @dev Thrown when a frozen account is the sender of a transfer
        error CreatorTokenTransferValidator__SenderAccountIsFrozen();
        /// @dev Thrown when validating a transfer for a collection that is in soulbound token mode.
        error CreatorTokenTransferValidator__TokenIsSoulbound();
        /// @dev Thrown when attempting to validate a permitted transfer where the permit type does not match the 
        ///      collection-defined token type.
        error CreatorTokenTransferValidator__TokenTypesDoNotMatch();
        /// @dev Thrown when an authorizer attempts to set a wildcard authorized operator on collections that don't 
        ///      allow wildcards
        error CreatorTokenTransferValidator__WildcardOperatorsCannotBeAuthorizedForCollection();// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "./DataTypes.sol";
        import "./Errors.sol";
        /**
         * @title ValidatorBase
         * @author Limit Break, Inc.
         * @notice Base contract for all Creator Token Validator, Module, and Ruleset contracts. 
         *         Includes some helper functions and modifiers and easy access to validator diamond storage
         *         to allow modules and rulesets to access the storage of the validator contract.
         */
        contract ValidatorBase {
            /*************************************************************************/
            /*                               MODIFIERS                               */
            /*************************************************************************/
            /**
             * @dev This modifier restricts a function call to the owner of the list `id`.
             * @dev Throws when the caller is not the list owner.
             */
            modifier onlyListOwner(uint48 id) {
                _requireCallerOwnsList(id);
                _;
            }
            /*************************************************************************/
            /*                                HELPERS                                */
            /*************************************************************************/
            /**
             * @notice Requires the caller to be the owner of list `id`.
             * 
             * @dev    Throws when the caller is not the owner of the list.
             * 
             * @param id  The id of the list to check ownership of.
             */
            function _requireCallerOwnsList(uint48 id) private view {
                if (msg.sender != validatorStorage().listOwners[id]) {
                    revert CreatorTokenTransferValidator__CallerDoesNotOwnList();
                }
            }
            /**
             * @dev Internal function used to compute the transient storage slot for the authorized 
             *      operator of a token in a collection.
             * 
             * @param collection The collection address of the token being transferred.
             * @param tokenId    The id of the token being transferred.
             * 
             * @return operatorSlot The storage slot location for the authorized operator value.
             */
            function _getTransientOperatorSlot(
                address collection, 
                uint256 tokenId
            ) internal pure returns (uint256 operatorSlot) {
                assembly {
                    mstore(0x00, collection)
                    mstore(0x20, tokenId)
                    operatorSlot := shr(4, keccak256(0x00, 0x40))
               }
            }
            /**
             * @dev Internal function used to compute the transient storage slot for the authorized operator of a collection.
             * 
             * @param collection The collection address of the token being transferred.
             * 
             * @return operatorSlot The storage slot location for the authorized operator value.
             */
            function _getTransientOperatorSlot(address collection) internal view returns (uint256 operatorSlot) {
                mapping (address => address) storage _transientOperator = validatorStorage().transientOperator;
                assembly {
                    mstore(0x00, collection)
                    mstore(0x20, _transientOperator.slot)
                    operatorSlot := keccak256(0x00, 0x40)
                }
            }
            /**
             * @dev Internal function used to efficiently retrieve the code length of `account`.
             * 
             * @param account The address to get the deployed code length for.
             * 
             * @return length The length of deployed code at the address.
             */
            function _getCodeLengthAsm(address account) internal view returns (uint256 length) {
                assembly { length := extcodesize(account) }
            }
            /**
             * @dev Internal function used to efficiently retrieve the codehash of `account`.
             * 
             * @param account The address to get the deployed codehash for.
             * 
             * @return codehash The codehash of the deployed code at the address.
             */
            function _getCodeHashAsm(address account) internal view returns (bytes32 codehash) {
                assembly { codehash := extcodehash(account) }
            }
            /**
             * @notice Returns true if the `flagValue` has the `flag` set, false otherwise.
             *
             * @dev    This function uses the bitwise AND operator to check if the `flag` is set in `flagValue`.
             *
             * @param flagValue  The value to check for the presence of the `flag`.
             * @param flag       The flag to check for in the `flagValue`.
             */
            function _isFlagSet(uint256 flagValue, uint256 flag) internal pure returns (bool flagSet) {
                flagSet = (flagValue & flag) != 0;
            }
            /*************************************************************************/
            /*                                STORAGE                                */
            /*************************************************************************/
            /// @dev The base storage slot for Validator V5 contract storage items.
            bytes32 constant DIAMOND_STORAGE_VALIDATOR = 
                0x0000000000000000000000000000000000000000000000000000000000721C05;
            /**
             * @dev Returns a storage object that follows the Diamond standard storage pattern for
             * @dev contract storage across multiple module contracts.
             */
            function validatorStorage() internal pure returns (ValidatorStorage storage diamondStorage) {
                bytes32 slot = DIAMOND_STORAGE_VALIDATOR;
                assembly {
                    diamondStorage.slot := slot
                }
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /** 
         * @title IRuleset
         * @author Limit Break, Inc.
         * @notice Interface for Creator Token Standards ruleset contracts.
         *
         * @dev Ruleset contracts are logic-gate contracts used to validate transfers of Creator Tokens
         *      (ERC20-C / ERC721-C / ERC1155-C). Rulesets are view-only, and may not contain any opcodes
         *      that could modify the state of the blockchain.
         */
        interface IRuleset {
            /**
             * @notice Validates a transfer of a Creator Token.
             *
             * @param authorizerCheckType The type of authorizer check to perform.
             * @param collection          The address of the Creator Token contract.
             * @param caller              The address of the caller (msg.sender) of the transfer function.
             * @param from                The address of the sender of the token.
             * @param to                  The address of the recipient of the token.
             * @param tokenId             The ID of the token.
             * @param amount              The amount of the token to transfer.
             * @return 0x00000000 when the transfer is allowed, or a custom error selector to block a transfer.
             */
            function validateTransfer(
                uint256 authorizerCheckType,
                address collection,
                address caller, 
                address from, 
                address to,
                uint256 tokenId,
                uint256 amount) external view returns (bytes4);
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /** 
         * @title IRulesetDelegateCall
         * @author Limit Break, Inc.
         * @notice Interface for delegate calling Creator Token Standards ruleset contracts.
         *
         * @dev Transfer validator implements this interface. It will STATICCALL itself to put itself into a 
         *      state where a ruleset check cannot possibly make any stateful changes, ensuring that they are always read-only.
         */
        interface IRulesetDelegateCall {
            /**
             * @notice Validates a transfer of a Creator Token in the context of a STATICCALL.
             *
             * @param authorizerCheckType The type of authorizer check to perform.
             * @param collection          The address of the Creator Token contract.
             * @param caller              The address of the caller (msg.sender) of the transfer function.
             * @param from                The address of the sender of the token.
             * @param to                  The address of the recipient of the token.
             * @param tokenId             The ID of the token.
             * @param amount              The amount of the token to transfer.
             * @return 0x00000000 when the transfer is allowed, or a custom error selector to block a transfer.
             * @return The token type (20, 721, 1155) for use in permit transfer checks when applicable.
             */
            function validateTransferDelegateCall(
                uint256 authorizerCheckType,
                address collection,
                address caller, 
                address from, 
                address to,
                uint256 tokenId,
                uint256 amount) external view returns (bytes4,uint16);
        }

        File 2 of 2: RulesetWhitelist
        pragma solidity ^0.8.4;
        /**
         * @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);
        }pragma solidity ^0.8.24;
        library StorageTstorish {   
            // keccak256(abi.encode(uint256(keccak256("storage.Tstorish")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant DATA_STORAGE_SLOT = 
                0xdacd49f6a6c42b45a5c3d195b83b324104542d9147bb8064a39c6a8d23ba9b00;
            struct Data {
                // Indicates if TSTORE support has been activated during or post-deployment.
                bool tstoreSupport;
            }
            function data() internal pure returns (Data storage ptr) {
                bytes32 slot = DATA_STORAGE_SLOT;
                assembly {
                    ptr.slot := slot
                }
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.24;
        import "./StorageTstorish.sol";
        /**
         * @title  Tloadish
         * @notice Based on https://github.com/ProjectOpenSea/tstorish/commit/a81ed74453ed7b9fe7e96a9906bc4def19b73e33
         */
        abstract contract Tloadish {
            /*
             * ------------------------------------------------------------------------+
             * Opcode      | Mnemonic         | Stack              | Memory            |
             * ------------------------------------------------------------------------|
             * 60 0x02     | PUSH1 0x02       | 0x02               |                   |
             * 60 0x1e     | PUSH1 0x1e       | 0x1e 0x02          |                   |
             * 61 0x3d5c   | PUSH2 0x3d5c     | 0x3d5c 0x1e 0x02   |                   |
             * 3d          | RETURNDATASIZE   | 0 0x3d5c 0x1e 0x02 |                   |
             *                                                                         |
             * :: store deployed bytecode in memory: (3d) RETURNDATASIZE (5c) TLOAD :: |
             * 52          | MSTORE           | 0x1e 0x02          | [0..0x20): 0x3d5c |
             * f3          | RETURN           |                    | [0..0x20): 0x3d5c |
             * ------------------------------------------------------------------------+
             */
            uint256 constant _TLOAD_TEST_PAYLOAD = 0x6002_601e_613d5c_3d_52_f3;
            uint256 constant _TLOAD_TEST_PAYLOAD_LENGTH = 0x0a;
            uint256 constant _TLOAD_TEST_PAYLOAD_OFFSET = 0x16;
            // Declare an immutable variable to store the tload test contract address.
            address private immutable _tloadTestContract;
            // Declare an immutable variable to store the initial TLOAD support status.
            bool internal immutable _tloadInitialSupport;
            // Declare an immutable function type variable for the _getTloadish function
            // based on chain support for tload at time of deployment.
            function(uint256) view returns (uint256) internal immutable _getTstorish;
            // Declare a few custom revert error types.
            error TloadTestContractDeploymentFailed();
            /**
             * @dev Determine TLOAD availability during deployment. This involves
             *      attempting to deploy a contract that utilizes TLOAD as part of the
             *      contract construction bytecode, and configuring initial support for
             *      using TLOAD in place of SLOAD based on the result.
             */
            constructor() {
                // Deploy the contract testing TLOAD support and store the address.
                address tloadTestContract = _prepareTloadTest();
                // Ensure the deployment was successful.
                if (tloadTestContract == address(0)) {
                    revert TloadTestContractDeploymentFailed();
                }
                // Determine if TLOAD is supported.
                _tloadInitialSupport = StorageTstorish.data().tstoreSupport = _testTload(tloadTestContract);
                if (_tloadInitialSupport) {
                    // If TLOAD is supported, set functions to their versions that use
                    // tload directly without support checks.
                    _getTstorish = _getTstore;
                } else {
                    // If TLOAD is not supported, set functions to their versions that 
                    // fallback to sload until tstoreSupport is true.
                    _getTstorish = _getTstorishWithSloadFallback;
                }
                // Set the address of the deployed TLOAD test contract as an immutable.
                _tloadTestContract = tloadTestContract;
            }
            /**
             * @dev Private function to read a TSTORISH value. Assigned to _getTstorish
             *      internal function variable at construction if chain has tload support.
             *
             * @param storageSlot The slot to read the TSTORISH value from.
             *
             * @return value The TSTORISH value at the given storage slot.
             */
            function _getTstore(
                uint256 storageSlot
            ) internal view returns (uint256 value) {
                assembly {
                    value := tload(storageSlot)
                }
            }
            /**
             * @dev Private function to read a TSTORISH value with sload fallback. 
             *      Assigned to _getTstorish internal function variable at construction
             *      if chain does not have tload support.
             *
             * @param storageSlot The slot to read the TSTORISH value from.
             *
             * @return value The TSTORISH value at the given storage slot.
             */
            function _getTstorishWithSloadFallback(
                uint256 storageSlot
            ) internal view returns (uint256 value) {
                if (StorageTstorish.data().tstoreSupport) {
                    assembly {
                        value := tload(storageSlot)
                    }
                } else {
                    assembly {
                        value := sload(storageSlot)
                    }
                }
            }
            /**
             * @dev Private function to deploy a test contract that utilizes TLOAD as
             *      part of its fallback logic.
             */
            function _prepareTloadTest() private returns (address contractAddress) {
                // Utilize assembly to deploy a contract testing TLOAD support.
                assembly {
                    // Write the contract deployment code payload to scratch space.
                    mstore(0, _TLOAD_TEST_PAYLOAD)
                    // Deploy the contract.
                    contractAddress := create(
                        0,
                        _TLOAD_TEST_PAYLOAD_OFFSET,
                        _TLOAD_TEST_PAYLOAD_LENGTH
                    )
                }
            }
            /**
             * @dev Private view function to determine if TLOAD is supported by
             *      the current EVM implementation by attempting to call the test
             *      contract, which utilizes TLOAD as part of its fallback logic.
             */
            function _testTload(
                address tloadTestContract
            ) private view returns (bool ok) {
                // Call the test contract, which will perform a TLOAD test. If the call
                // does not revert, then TLOAD is supported. Do not forward all
                // available gas, as all forwarded gas will be consumed on revert.
                (ok, ) = tloadTestContract.staticcall{ gas: gasleft() / 10 }("");
            }
        }pragma solidity ^0.8.4;
        /**
         * @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.
         *
         * ```solidity
         * contract Example {
         *     // Add the library methods
         *     using EnumerableSet for EnumerableSet.AddressSet;
         *
         *     // Declare a set state variable
         *     EnumerableSet.AddressSet private mySet;
         * }
         * ```
         *
         * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
         * and `uint256` (`UintSet`) are supported.
         *
         * [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 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;
            }
            // 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 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;
                /// @solidity memory-safe-assembly
                assembly {
                    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 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;
                /// @solidity memory-safe-assembly
                assembly {
                    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 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;
                /// @solidity memory-safe-assembly
                assembly {
                    result := store
                }
                return result;
            }
        }pragma solidity ^0.8.4;
        import "../introspection/IERC165.sol";
        interface IEOARegistry is IERC165 {
            function isVerifiedEOA(address account) external view returns (bool);
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /**************************************************************/
        /*                        LIST TYPES                          */
        /**************************************************************/
        uint8 constant LIST_TYPE_BLACKLIST = 0;
        uint8 constant LIST_TYPE_WHITELIST = 1;
        uint8 constant LIST_TYPE_AUTHORIZERS = 2;
        uint8 constant EXPANSION_LIST_TYPE_WHITELIST_EXTENSION_CONTRACTS = 3;
        uint8 constant EXPANSION_LIST_TYPE_7702_DELEGATE_WHITELIST = 4;
        uint8 constant EXPANSION_LIST_TYPE_7702_DELEGATE_WHITELIST_EXTENSION_CONTRACTS = 5;
        /**************************************************************/
        /*                        RULESET IDS                         */
        /**************************************************************/
        uint8 constant RULESET_ID_DEFAULT = 0;
        uint8 constant RULESET_ID_VANILLA = 1;
        uint8 constant RULESET_ID_SOULBOUND = 2;
        uint8 constant RULESET_ID_BLACKLIST = 3;
        uint8 constant RULESET_ID_WHITELIST = 4;
        uint8 constant RULESET_ID_FIXED_OR_CUSTOM = 255;
        /**************************************************************/
        /*                    AUTHORIZER CHECK TYPES                  */
        /**************************************************************/
        uint256 constant AUTHORIZER_CHECK_TYPE_TOKEN = 1;
        uint256 constant AUTHORIZER_CHECK_TYPE_COLLECTION = 2;
        uint256 constant AUTHORIZER_CHECK_TYPE_TOKEN_AND_AMOUNT = 3;
        /**************************************************************/
        /*                       MISCELLANEOUS                        */
        /**************************************************************/
        bytes4 constant LEGACY_TRANSFER_VALIDATOR_INTERFACE_ID = bytes4(0x00000000);
        bytes4 constant SELECTOR_NO_ERROR = bytes4(0x00000000);
        bytes32 constant BYTES32_ZERO = 0x0000000000000000000000000000000000000000000000000000000000000000;
        bytes32 constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x0000000000000000000000000000000000000000000000000000000000000000;
        address constant WILDCARD_OPERATOR_ADDRESS = address(0x01);
        uint48 constant DEFAULT_LIST_ID = 0;
        uint16 constant DEFAULT_TOKEN_TYPE = 0;
        /**************************************************************/
        /*                       FLAGS - GLOBAL                       */
        /**************************************************************/
        // Flags are used to efficiently store and retrieve boolean values in a single uint8.
        // Each flag is a power of 2, so they can be combined using bitwise OR (|) and checked using bitwise AND (&).
        // For example, to set the first and third flags, you would use: flags = FLAG1 | FLAG3;
        // To check if the first flag is set, you would use: if (flags & FLAG1 != 0) { ... }
        uint8 constant FLAG_GLOBAL_DISABLE_AUTHORIZATION_MODE = 1 << 0;
        uint8 constant FLAG_GLOBAL_AUTHORIZERS_CANNOT_SET_WILDCARD_OPERATORS = 1 << 1;
        uint8 constant FLAG_GLOBAL_ENABLE_ACCOUNT_FREEZING_MODE = 1 << 2;
        uint8 constant FLAG_GLOBAL_CUSTOM_LIST_SUPPLEMENTS_DEFAULT_LIST = 1 << 3;
        /**************************************************************/
        /*                  FLAGS - RULESET WHITELIST                 */
        /**************************************************************/
        uint16 constant FLAG_RULESET_WHITELIST_BLOCK_ALL_OTC = 1 << 0;
        uint16 constant FLAG_RULESET_WHITELIST_ALLOW_OTC_FOR_7702_DELEGATES = 1 << 1;
        uint16 constant FLAG_RULESET_WHITELIST_ALLOW_OTC_FOR_SMART_WALLETS = 1 << 2;
        uint16 constant FLAG_RULESET_WHITELIST_BLOCK_SMART_WALLET_RECEIVERS = 1 << 3;
        uint16 constant FLAG_RULESET_WHITELIST_BLOCK_UNVERIFIED_EOA_RECEIVERS = 1 << 4;// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "@limitbreak/tm-core-lib/src/utils/structs/EnumerableSet.sol";
        /**
         * @dev This struct contains the security policy settings for a collection.
         */
        struct CollectionSecurityPolicy {
            uint8 rulesetId;
            uint48 listId;
            address customRuleset;
            uint8 globalOptions;
            uint16 rulesetOptions;
            uint16 tokenType;
        }
        /**
         * @dev This struct is internally for the storage of account and codehash lists.
         */
        struct List {
            EnumerableSet.AddressSet enumerableAccounts;
            EnumerableSet.Bytes32Set enumerableCodehashes;
            mapping (address => bool) nonEnumerableAccounts;
            mapping (bytes32 => bool) nonEnumerableCodehashes;
        }
        /**
         * @dev This struct is internally for the storage of account lists.
         */
        struct AccountList {
            EnumerableSet.AddressSet enumerableAccounts;
            mapping (address => bool) nonEnumerableAccounts;
        }
        /**
         * @dev This struct contains a key and value pair for future expansion data words that are 32 bytes long.
         */
        struct ExpansionWord {
            bytes32 key;
            bytes32 value;
        }
        /**
         * @dev This struct contains a key and value pair for future expansion data bytes that are variable length.
         */
        struct ExpansionDatum {
            bytes32 key;
            bytes value;
        }
        /**
         * @dev This struct contains the storage layout for the validator contract, excluding Permit-C and Tstorish data.
         */
        struct ValidatorStorage {
            /// @notice Keeps track of the most recently created list id.
            uint48 lastListId;
            /// @dev Used as a collision guard.
            mapping (address => address) transientOperator;
            /// @notice Mapping of list ids to list owners
            mapping (uint48 => address) listOwners;
            /// @dev Mapping of collection addresses to their security policy settings
            mapping (address => CollectionSecurityPolicy) collectionSecurityPolicies;
            /// @dev Mapping of collections to accounts that are frozen for those collections
            mapping (address => AccountList) frozenAccounts;
            /// @dev Mapping of list ids to list data
            mapping (uint8 => mapping (uint48 => List)) lists;
            /// @dev Mapping of collection addressses to any future expansion data words settings that may be used in the future
            mapping (address collection => mapping (bytes32 extension => bytes32 word)) collectionExpansionWords;
            /// @dev Mapping of collection addressses to any future expansion data bytes settings that may be used in the future
            mapping (address collection => mapping (bytes32 extension => bytes data)) collectionExpansionDatums;
            /// @dev Mapping of addresses to a boolean indicating if they are registered trusted validator modules
            mapping (address => bool) registeredRulesets;
            /// @dev Mapping of security levels to their current implementation modules
            mapping (uint8 => address) rulesetBindings;
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /// @dev Thrown when admin attempts to bind a ruleset to the fixed/custom ruleset ID.
        error CreatorTokenTransferValidator__AdminCannotAssignRulesetToRulesetIdCustom();
        /// @dev Thrown when validating transfers with amount if authorization by amount mode is active and the amount
        ///      exceeds the pre-authorized amount.
        error CreatorTokenTransferValidator__AmountExceedsAuthorization();
        /// @dev Thrown when attempting to set a authorized operator when authorization mode is disabled.
        error CreatorTokenTransferValidator__AuthorizationDisabledForCollection();
        /// @dev Thrown when attempting to call a function that requires the caller to be the list owner.
        error CreatorTokenTransferValidator__CallerDoesNotOwnList();
        /// @dev Thrown when authorizing a transfer for a collection using authorizers and the msg.sender is not in the 
        ///      authorizer list.
        error CreatorTokenTransferValidator__CallerMustBeAnAuthorizer();
        /// @dev Thrown when validating a transfer for a collection using whitelists and the operator is not on the whitelist.
        error CreatorTokenTransferValidator__CallerMustBeWhitelisted();
        /// @dev Thrown when attempting to call a function that requires owner or default admin role for a collection that the 
        ///      caller does not have.
        error CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
        /// @dev Thrown when validating a transfer for a collection using whitelists and the operator or from account is not on the whitelist.
        error CreatorTokenTransferValidator__CallerOrFromMustBeWhitelisted();
        /// @dev Thrown when attempting to renounce ownership of the default list id.
        error CreatorTokenTransferValidator__CannotRenounceOwnershipOfDefaultList();
        /// @dev Thrown when setting the ruleset for a collection when a reserved ruleset id is used, but a custom ruleset
        ///      is specified.
        error CreatorTokenTransferValidator__CannotSetCustomRulesetOnManagedRulesetId();
        /// @dev Thrown when constructor args are not valid
        error CreatorTokenTransferValidator__InvalidConstructorArgs();
        /// @dev Thrown when setting the transfer security level to an invalid value.
        error CreatorTokenTransferValidator__InvalidTransferSecurityLevel();
        /// @dev Thrown when attempting to set a list id that does not exist.
        error CreatorTokenTransferValidator__ListDoesNotExist();
        /// @dev Thrown when attempting to transfer the ownership of a list to the zero address.
        error CreatorTokenTransferValidator__ListOwnershipCannotBeTransferredToZeroAddress();
        /// @dev Thrown when attempting to call the transfer validation logic externally, as staticcall guarantees are needed.
        error CreatorTokenTransferValidator__OnlyValidatorCanAccessThisFunction();
        /// @dev Thrown when validating a transfer for a collection using blacklists and the operator is on the blacklist.
        error CreatorTokenTransferValidator__OperatorIsBlacklisted();
        /// @dev Thrown when validating an OTC transfer with EIP-7702 Delegation when disabled by security settings.
        error CreatorTokenTransferValidator__OTCNotAllowedFor7702Delegates();
        /// @dev Thrown when validating an OTC transfer from Smart Wallets when disabled by security settings.
        error CreatorTokenTransferValidator__OTCNotAllowedForSmartWallets();
        /// @dev Thrown when a frozen account is the receiver of a transfer
        error CreatorTokenTransferValidator__ReceiverAccountIsFrozen();
        /// @dev Thrown when validating a transfer for a collection that does not allow receiver to have code and the receiver 
        ///      has code.
        error CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode();
        /// @dev Thrown when validating a transfer for a collection that requires receivers be verified EOAs and the receiver 
        ///      is not verified.
        error CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified();
        /// @dev Thrown when attempting to register a ruleset that is not a contract.
        error CreatorTokenTransferValidator__RulesetIsNotContract();
        /// @dev Thrown when attempting to register a ruleset that is not pure.
        error CreatorTokenTransferValidator__RulesetIsNotPure();
        /// @dev Thrown when admin attempts to bind a ruleset that is not registered, or a collection admin tries to 
        ///      set their fixed/custom ruleset to an unregistered ruleset.
        error CreatorTokenTransferValidator__RulesetIsNotRegistered();
        /// @dev Thrown when a frozen account is the sender of a transfer
        error CreatorTokenTransferValidator__SenderAccountIsFrozen();
        /// @dev Thrown when validating a transfer for a collection that is in soulbound token mode.
        error CreatorTokenTransferValidator__TokenIsSoulbound();
        /// @dev Thrown when attempting to validate a permitted transfer where the permit type does not match the 
        ///      collection-defined token type.
        error CreatorTokenTransferValidator__TokenTypesDoNotMatch();
        /// @dev Thrown when an authorizer attempts to set a wildcard authorized operator on collections that don't 
        ///      allow wildcards
        error CreatorTokenTransferValidator__WildcardOperatorsCannotBeAuthorizedForCollection();// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "./DataTypes.sol";
        import "./Errors.sol";
        /**
         * @title ValidatorBase
         * @author Limit Break, Inc.
         * @notice Base contract for all Creator Token Validator, Module, and Ruleset contracts. 
         *         Includes some helper functions and modifiers and easy access to validator diamond storage
         *         to allow modules and rulesets to access the storage of the validator contract.
         */
        contract ValidatorBase {
            /*************************************************************************/
            /*                               MODIFIERS                               */
            /*************************************************************************/
            /**
             * @dev This modifier restricts a function call to the owner of the list `id`.
             * @dev Throws when the caller is not the list owner.
             */
            modifier onlyListOwner(uint48 id) {
                _requireCallerOwnsList(id);
                _;
            }
            /*************************************************************************/
            /*                                HELPERS                                */
            /*************************************************************************/
            /**
             * @notice Requires the caller to be the owner of list `id`.
             * 
             * @dev    Throws when the caller is not the owner of the list.
             * 
             * @param id  The id of the list to check ownership of.
             */
            function _requireCallerOwnsList(uint48 id) private view {
                if (msg.sender != validatorStorage().listOwners[id]) {
                    revert CreatorTokenTransferValidator__CallerDoesNotOwnList();
                }
            }
            /**
             * @dev Internal function used to compute the transient storage slot for the authorized 
             *      operator of a token in a collection.
             * 
             * @param collection The collection address of the token being transferred.
             * @param tokenId    The id of the token being transferred.
             * 
             * @return operatorSlot The storage slot location for the authorized operator value.
             */
            function _getTransientOperatorSlot(
                address collection, 
                uint256 tokenId
            ) internal pure returns (uint256 operatorSlot) {
                assembly {
                    mstore(0x00, collection)
                    mstore(0x20, tokenId)
                    operatorSlot := shr(4, keccak256(0x00, 0x40))
               }
            }
            /**
             * @dev Internal function used to compute the transient storage slot for the authorized operator of a collection.
             * 
             * @param collection The collection address of the token being transferred.
             * 
             * @return operatorSlot The storage slot location for the authorized operator value.
             */
            function _getTransientOperatorSlot(address collection) internal view returns (uint256 operatorSlot) {
                mapping (address => address) storage _transientOperator = validatorStorage().transientOperator;
                assembly {
                    mstore(0x00, collection)
                    mstore(0x20, _transientOperator.slot)
                    operatorSlot := keccak256(0x00, 0x40)
                }
            }
            /**
             * @dev Internal function used to efficiently retrieve the code length of `account`.
             * 
             * @param account The address to get the deployed code length for.
             * 
             * @return length The length of deployed code at the address.
             */
            function _getCodeLengthAsm(address account) internal view returns (uint256 length) {
                assembly { length := extcodesize(account) }
            }
            /**
             * @dev Internal function used to efficiently retrieve the codehash of `account`.
             * 
             * @param account The address to get the deployed codehash for.
             * 
             * @return codehash The codehash of the deployed code at the address.
             */
            function _getCodeHashAsm(address account) internal view returns (bytes32 codehash) {
                assembly { codehash := extcodehash(account) }
            }
            /**
             * @notice Returns true if the `flagValue` has the `flag` set, false otherwise.
             *
             * @dev    This function uses the bitwise AND operator to check if the `flag` is set in `flagValue`.
             *
             * @param flagValue  The value to check for the presence of the `flag`.
             * @param flag       The flag to check for in the `flagValue`.
             */
            function _isFlagSet(uint256 flagValue, uint256 flag) internal pure returns (bool flagSet) {
                flagSet = (flagValue & flag) != 0;
            }
            /*************************************************************************/
            /*                                STORAGE                                */
            /*************************************************************************/
            /// @dev The base storage slot for Validator V5 contract storage items.
            bytes32 constant DIAMOND_STORAGE_VALIDATOR = 
                0x0000000000000000000000000000000000000000000000000000000000721C05;
            /**
             * @dev Returns a storage object that follows the Diamond standard storage pattern for
             * @dev contract storage across multiple module contracts.
             */
            function validatorStorage() internal pure returns (ValidatorStorage storage diamondStorage) {
                bytes32 slot = DIAMOND_STORAGE_VALIDATOR;
                assembly {
                    diamondStorage.slot := slot
                }
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /** 
         * @title IRuleset
         * @author Limit Break, Inc.
         * @notice Interface for Creator Token Standards ruleset contracts.
         *
         * @dev Ruleset contracts are logic-gate contracts used to validate transfers of Creator Tokens
         *      (ERC20-C / ERC721-C / ERC1155-C). Rulesets are view-only, and may not contain any opcodes
         *      that could modify the state of the blockchain.
         */
        interface IRuleset {
            /**
             * @notice Validates a transfer of a Creator Token.
             *
             * @param authorizerCheckType The type of authorizer check to perform.
             * @param collection          The address of the Creator Token contract.
             * @param caller              The address of the caller (msg.sender) of the transfer function.
             * @param from                The address of the sender of the token.
             * @param to                  The address of the recipient of the token.
             * @param tokenId             The ID of the token.
             * @param amount              The amount of the token to transfer.
             * @return 0x00000000 when the transfer is allowed, or a custom error selector to block a transfer.
             */
            function validateTransfer(
                uint256 authorizerCheckType,
                address collection,
                address caller, 
                address from, 
                address to,
                uint256 tokenId,
                uint256 amount) external view returns (bytes4);
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        /** 
         * @title IRuleset
         * @author Limit Break, Inc.
         * @notice Interface for whitelist extension contracts for the Whitelist Ruleset.
         *
         * @dev The Whitelist Ruleset supports whitelisting of specific accounts and codehashes.  
         *      A Whitelist Extension contract offers an additional mechanism to perform more sophisticated
         *      checks to see if an account can be allowed to perform a transfer.  For example, imagine a collection
         *      that blocks OTC transfers from smart wallet accounts because a smart wallet could be used to faciliate
         *      a trade that violates the creator's rules on trading.  A Whitelist Extension contract could be used to
         *      query a trusted smart wallet factory that is known to adhere to the creator's rules, and allow any wallet
         *      originating from that factory to perform OTC trades or to receive tokens when in a mode that blocks contracts
         *      from receiving tokens.
         */
         
        interface IWhitelistExtension {
            
            /**
             * @notice Perform an advanced check on an account to see if it should be whitelisted.
             *
             * @param collection The address of the Creator Token contract.
             * @param account    The address of the account to check.
             * @return true if the account is whitelisted, false otherwise.
             */
            function isWhitelisted(
                address collection,
                address account) external view returns (bool);
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "../Constants.sol";
        import "../DataTypes.sol";
        import "../Errors.sol";
        import "../ValidatorBase.sol";
        import "../interfaces/IRuleset.sol";
        import "@limitbreak/tm-core-lib/src/utils/misc/Tloadish.sol";
        /** 
        * @title RulesetBase
        * @author Limit Break, Inc.
        * @notice Base contract that includes internal helper functions and diamond storage access that are
        *         useful for implementations of Creator Token Standards ruleset contracts.
        */ 
        abstract contract RulesetBase is ValidatorBase, Tloadish {
            /**
             * @dev Internal function to check if a caller is authorized to transfer a token/id/amount.
             * 
             * @param authorizerCheckType The type of authorizer check to perform.
             * @param collection          The address of the Creator Token contract.
             * @param caller              The address of the caller (msg.sender) of the transfer function.
             * @param tokenId             The ID of the token.
             * @param amount              The amount of the token to transfer.
             * 
             * @return isAuthorized       True if the caller is authorized to transfer the token, false otherwise.
             */
            function _isAuthorized(
                uint256 authorizerCheckType, 
                address collection,
                address caller,
                uint256 tokenId,
                uint256 amount
            ) internal view returns (bool isAuthorized) {
                isAuthorized = _getAuthorizerCheckFunctionPointer(authorizerCheckType)(
                    collection, caller, tokenId, amount
                );
            }
            /**
             * @dev Internal function to get the function pointer for the authorizer check type.
             * 
             * @param authorizerCheckType The type of authorizer check to perform.
             * 
             * @return _callerAuthorizedCheck  The function pointer to the authorizer check function.
             */
            function _getAuthorizerCheckFunctionPointer(uint256 authorizerCheckType) internal pure returns (function(address,address,uint256, uint256) internal view returns(bool) _callerAuthorizedCheck) {
                if (authorizerCheckType == AUTHORIZER_CHECK_TYPE_TOKEN) {
                    return _callerAuthorizedCheckToken;
                } else if (authorizerCheckType == AUTHORIZER_CHECK_TYPE_COLLECTION) {
                    return _callerAuthorizedCheckCollection;
                } else {
                    // The master validator handles the authorized amount check, so we won't do anything different here to use
                    // the authorized amount and the amount parameter.
                    // Current rulesets won't use this call, it is a placeholder for future ruleset use.
                    return _callerAuthorizedCheckCollection;
                }
            }
            /**
             * @dev Internal function to check if a caller is an authorized operator for the token being transferred.
             * 
             * @param caller     The caller of the token transfer.
             * @param collection The collection address of the token being transferred.
             * @param tokenId    The id of the token being transferred.
             * 
             * @return isAuthorized  True if the caller is authorized to transfer the token, false otherwise.
             */
            function _callerAuthorizedCheckToken(
                address collection,
                address caller,
                uint256 tokenId,
                uint256 /*amount*/
            ) internal view returns (bool isAuthorized) {
                uint256 slotValue;
                (isAuthorized, ) = _callerAuthorized(caller, _getTransientOperatorSlot(collection, tokenId));
                if (isAuthorized) return true;
                (isAuthorized, slotValue) = _callerAuthorized(caller, _getTransientOperatorSlot(collection));
                isAuthorized = isAuthorized && slotValue >> 255 == 1;
            }
            /**
             * @dev Internal function to check if a caller is an authorized operator for the collection being transferred.
             * 
             * @param caller     The caller of the token transfer.
             * @param collection The collection address of the token being transferred.
             * 
             * @return isAuthorized  True if the caller is authorized to transfer the collection, false otherwise.
             */
            function _callerAuthorizedCheckCollection(
                address collection,
                address caller,
                uint256 /*tokenId*/,
                uint256 /*amount*/
            ) internal view returns (bool isAuthorized) {
                (isAuthorized, ) = _callerAuthorized(caller, _getTransientOperatorSlot(collection));
            }
            /**
             * @dev Internal function to check if a caller is an authorized operator. 
             * @dev This overload of `_callerAuthorized` checks a specific storage slot for the caller address.
             * 
             * @param caller     The caller of the token transfer.
             * @param slot       The storage slot to check for the caller address.
             * 
             * @return isAuthorized  True if the caller is authorized to transfer the token, false otherwise.
             * @return slotValue     The transient storage value in `slot`, used to check for allow any token id flag if necessary.
             */
            function _callerAuthorized(address caller, uint256 slot) internal view returns (bool isAuthorized, uint256 slotValue) {
                slotValue = _getTstorish(slot);
                address authorizedOperator = address(uint160(slotValue));
                isAuthorized = authorizedOperator == WILDCARD_OPERATOR_ADDRESS || authorizedOperator == caller;
            }
            /**
             * @dev Internal function to check if an address is an EOA in EIP-7702 delegation mode, and what address
             *      it is delegated to.
             * 
             * @param _address   The account to check for EIP-7702 delegation.
             * 
             * @return isDelegate True if the account is an EOA with an EIP-7702 delegate attached, false otherwise.
             * @return delegate   The address of the EIP-7702 delegate code, if one is attached.
             */
            function _check7702(address _address) internal view returns (bool isDelegate, address delegate) {
                assembly ("memory-safe") {
                    mstore(0x00, 0x00)
                    extcodecopy(_address, 0x1D, 0x00, 0x17)
                    if eq(mload(0x00), 0xEF0100) {
                        isDelegate := 1
                        delegate := shr(0x60, mload(0x20))
                    }
                }
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import "./RulesetBase.sol";
        import "../interfaces/IWhitelistExtension.sol";
        import "@limitbreak/tm-core-lib/src/utils/token/IEOARegistry.sol";
        /**
         * @title RulesetWhitelist
         * @author Limit Break, Inc.
         * @notice A ruleset contract that blocks transfers by default, unless exceptions are made through various options and 
         *         whitelisting rules.  
         */
        contract RulesetWhitelist is IRuleset, RulesetBase {
            using EnumerableSet for EnumerableSet.AddressSet;
            using EnumerableSet for EnumerableSet.Bytes32Set;
            struct Options {
                bool enableAccountFreezingMode;
                bool disableAuthorizationMode;
                bool customListSupplementsDefaultList;
                bool blockSmartWalletReceivers;
                bool blockUnverifiedEOAReceivers;
                bool blockAllOTC;
                bool allowOTCFor7702Delegates;
                bool allowOTCForSmartWallets;
            }
            /// @dev The address of the EOA Registry to use to validate an account is a verified EOA.
            address private immutable _eoaRegistry;
            constructor(address eoaRegistry_) RulesetBase() {
                if (eoaRegistry_ == address(0)) {
                    revert CreatorTokenTransferValidator__InvalidConstructorArgs();
                }
                _eoaRegistry = eoaRegistry_;
            }
            /**
             * @notice Validates a transfer of a Creator Token.
             *
             * @notice Global Options Used: 
             *         - Disable Authorization Mode: Toggles authorization mode logic on/off (default: enabled)
             *         - Enable Account Freezing Mode: Toggles account freezing logic on/off (default: disabled)     
             *         - Custom List Supplements Default List Mode: When toggled on, whitelist includes both custom list 
             *                                                      values and default managed list values (default: off)
             *
             * @notice Ruleset-Specific Options Used: 
             *         - Block Smart Wallet Receivers: When enabled, blocks transfers to receivers with 
             *                                         code length > 0, unless `to` is whitelisted (default: disabled)
             *         - Block Unverified EOA Receivers: When enabled, blocks transfers to receivers that have not verified
             *                                           an ECDSA signature on the EOA registry, unless `to` is whitelisted 
             *                                           (default: disabled)
             *         - Block All OTC: When enabled, blocks all OTC transfers, unless `caller` or `from` are whitelisted 
             *                          (default: disabled) 
             *                          [OTC transfer is defined as any owner-initiated transfer where caller == from]
             *         - Allow OTC For 7702 Delegates: Unused when Block All OTC is enabled. When enabled, allows OTC transfers 
             *                                         from EOAs that currently have EIP-7702 delegates attached. When disabled,
             *                                         blocks OTC transfers from EOAs that currently have EIP-7702 delegates 
             *                                         attached, unless the attached delegate is whitelisted. 
             *                                         (default: disabled)
             *         - Allow OTC For Smart Wallets: Unused when Block All OTC is enabled. When enabled, allows OTC transfers
             *                                        from smart wallets. When disabled, blocks OTC transfers from smart wallets,
             *                                        unless the smart wallet is whitelisted. (default: disabled)
             *
             * @notice Validation Flow: 
             *         1. If account freezing mode is enabled, check if the sender or recipient is frozen.
             *            If frozen, block transfer and return.
             *         2. If authorization mode is enabled, check if an authorizer has pre-authorized transfer of 
             *            collection/token id/amount by operator (msg.sender). If authorized, allow transfer and return.
             *         3. If block smart wallet receivers is enabled, check if the receiver has code length > 0. If so,
             *            check if the receiver is whitelisted. If not whitelisted, block transfer and return. 
             *            If whitelisted, continue.
             *         4. If block unverified EOA receivers is enabled, check if the receiver has verified an ECDSA signature
             *            on the EOA registry. If not verified, check if the receiver is whitelisted. If not whitelisted, 
             *            block transfer and return. If whitelisted, continue.
             *         5. If transfer is OTC (caller == from):
             *            5a. If block all OTC is enabled, check if `caller` or `from` are whitelisted.  
             *                If not whitelisted, block transfer and return.  If whitelisted, allow transfer and return.
             *            5b. If block all OTC is disabled, and code length of `from` address equals 0, allow transfer and return.
             *            5c. If block all OTC is disabled, and code length of `from` address is greater than 0:
             *                5ci. If `from` is an EOA with delegation attached: when allow OTC for 7702 delegates is enabled, 
             *                     allow transfer an return.  Otherwise, when allow OTC for 7702 delegates is disabled, check
             *                     if delegate is whitelisted. If not whitelisted, block transfer and return. If whitelisted,
             *                     allow transfer and return.
             *                5cii. Otherwise, `from` is a smart wallet. If allow OTC for smart wallets is enabled, allow 
             *                      transfer and return. Otherwise, check if `from` is whitelisted. If not whitelisted, block
             *                      transfer and return. If whitelisted, allow transfer and return.
             *          6. If transfer is not OTC (caller != from), check if `caller` is whitelisted. If not whitelisted, 
             *             block transfer and return. If whitelisted, allow transfer and return.
             *
             * @notice Whitelisting Notes:
             *         - When performing a whitelist check, when custom list supplements default list mode is enabled, the
             *           default whitelist is checked first.  If the operator is not whitelisted in the default list, the
             *           custom list specified in the collection security policy is checked.
             *         - When performing whitelist checks, accounts are first checked by address, then by codehash, and finally
             *           evaluated against a set of 0 or more whitelist extensions.
             */
            function validateTransfer(
                uint256 authorizerCheckType,
                address collection,
                address caller, 
                address from,
                address to,
                uint256 tokenId,
                uint256 amount) external view returns (bytes4) {
                CollectionSecurityPolicy storage collectionSecurityPolicy = 
                    validatorStorage().collectionSecurityPolicies[collection];
                Options memory opts = 
                    _getOptions(collectionSecurityPolicy.globalOptions, collectionSecurityPolicy.rulesetOptions);
                if (opts.enableAccountFreezingMode) {
                    AccountList storage frozenAccountList = validatorStorage().frozenAccounts[collection];
                    
                    if (frozenAccountList.nonEnumerableAccounts[from]) {
                        return CreatorTokenTransferValidator__SenderAccountIsFrozen.selector;
                    }
                    if (frozenAccountList.nonEnumerableAccounts[to]) {
                        return CreatorTokenTransferValidator__ReceiverAccountIsFrozen.selector;
                    }
                }
                if (!opts.disableAuthorizationMode) {
                    if (_isAuthorized(authorizerCheckType, collection, caller, tokenId, amount)) {
                        return SELECTOR_NO_ERROR;
                    }
                }
                return _doWhitelistChecks(
                    opts, 
                    collectionSecurityPolicy.listId,
                    collection, 
                    to, 
                    caller, 
                    from);
            }
            function _doWhitelistChecks(
                Options memory opts,
                uint48 listId,
                address collection,
                address to,
                address caller, 
                address from
            ) internal view returns (bytes4) {
                (
                    mapping (address => bool) storage accountWhitelist,
                    mapping (bytes32 => bool) storage codehashWhitelist,
                    EnumerableSet.AddressSet storage whitelistExtensions
                ) = _getWhitelistStoragePointers(listId);
                if (opts.blockSmartWalletReceivers) {
                    if (_getCodeLengthAsm(to) > 0) {
                        if (!_isWhitelisted(opts.customListSupplementsDefaultList, accountWhitelist, codehashWhitelist, whitelistExtensions, collection, to)) {
                            return CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode.selector;
                        }
                    }
                }
                if (opts.blockUnverifiedEOAReceivers) {
                    if (!_isVerifiedEOA(to)) {
                        if (!_isWhitelisted(opts.customListSupplementsDefaultList, accountWhitelist, codehashWhitelist, whitelistExtensions, collection, to)) {
                            return CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified.selector;
                        }
                    }
                }
                if (caller == from) {
                    if (!opts.blockAllOTC) {
                        if (_getCodeLengthAsm(from) > 0) {
                            (bool isDelegate, address delegate) = _check7702(from);
                            if (opts.allowOTCFor7702Delegates) {
                                // If allowing OTC for 7702 delegates, check if the owner is delegated or not
                                if (!isDelegate) {
                                    // This is a non-delegated smart wallet account, if OTC is not allowed for smart wallets check against whitelist
                                    if (!opts.allowOTCForSmartWallets) {
                                        if (!_isWhitelisted(opts.customListSupplementsDefaultList, accountWhitelist, codehashWhitelist, whitelistExtensions, collection, from)) {
                                            return CreatorTokenTransferValidator__OTCNotAllowedForSmartWallets.selector;
                                        }
                                    }
                                }
                            } else {
                                // If not allowing OTC for 7702 delegates, check if the owner is delegated or not
                                if (isDelegate) { 
                                    // This is a delegated EOA account, block unless delegate is whitelisted
                                    if (!_is7702DelegateWhitelisted(opts.customListSupplementsDefaultList, listId, collection, delegate)) {
                                        return CreatorTokenTransferValidator__OTCNotAllowedFor7702Delegates.selector;
                                    }
                                } else {
                                    // This is a non-delegated smart wallet account, if OTC is not allowed for smart wallets check against whitelist
                                    if (!opts.allowOTCForSmartWallets) {
                                        if (!_isWhitelisted(opts.customListSupplementsDefaultList, accountWhitelist, codehashWhitelist, whitelistExtensions, collection, from)) {
                                            return CreatorTokenTransferValidator__OTCNotAllowedForSmartWallets.selector;
                                        }
                                    }
                                }
                            }
                        }
                        return SELECTOR_NO_ERROR;
                    }
                }
                if (!_isWhitelisted(opts.customListSupplementsDefaultList, accountWhitelist, codehashWhitelist, whitelistExtensions, collection, caller, from)) {
                    return CreatorTokenTransferValidator__CallerOrFromMustBeWhitelisted.selector;
                }
                return SELECTOR_NO_ERROR;
            }
            function _isWhitelisted(
                bool customListSupplementsDefaultList,
                mapping (address => bool) storage accountWhitelist,
                mapping (bytes32 => bool) storage codehashWhitelist,
                EnumerableSet.AddressSet storage whitelistExtensions,
                address collection,
                address account
            ) internal view returns (bool isWhitelisted) {
                if (customListSupplementsDefaultList) {
                    (
                        mapping (address => bool) storage defaultAccountWhitelist,
                        mapping (bytes32 => bool) storage defaultCodehashWhitelist,
                        EnumerableSet.AddressSet storage defaultWhitelistExtensions
                    ) = _getWhitelistStoragePointers(DEFAULT_LIST_ID);
                    if (_checkWhitelist(
                            defaultAccountWhitelist, 
                            defaultCodehashWhitelist, 
                            defaultWhitelistExtensions, 
                            collection, 
                            account)) {
                        return true;
                    }
                }
                return _checkWhitelist(accountWhitelist, codehashWhitelist, whitelistExtensions, collection, account);
            }
            function _isWhitelisted(
                bool customListSupplementsDefaultList,
                mapping (address => bool) storage accountWhitelist,
                mapping (bytes32 => bool) storage codehashWhitelist,
                EnumerableSet.AddressSet storage whitelistExtensions,
                address collection,
                address account1,
                address account2
            ) internal view returns (bool isWhitelisted) {
                if (customListSupplementsDefaultList) {
                    (
                        mapping (address => bool) storage defaultAccountWhitelist,
                        mapping (bytes32 => bool) storage defaultCodehashWhitelist,
                        EnumerableSet.AddressSet storage defaultWhitelistExtensions
                    ) = _getWhitelistStoragePointers(DEFAULT_LIST_ID);
                    if (_checkWhitelist(
                            defaultAccountWhitelist, 
                            defaultCodehashWhitelist, 
                            defaultWhitelistExtensions, 
                            collection, 
                            account1,
                            account2)) {
                        return true;
                    }
                }
                return _checkWhitelist(accountWhitelist, codehashWhitelist, whitelistExtensions, collection, account1, account2);
            }
            function _is7702DelegateWhitelisted(
                bool customListSupplementsDefaultList,
                uint48 listId,
                address collection,
                address account
            ) internal view returns (bool isWhitelisted) {
                if (customListSupplementsDefaultList) {
                    (
                        mapping (address => bool) storage defaultAccountWhitelist,
                        mapping (bytes32 => bool) storage defaultCodehashWhitelist,
                        EnumerableSet.AddressSet storage defaultWhitelistExtensions
                    ) = _get7702DelegateWhitelistStoragePointers(DEFAULT_LIST_ID);
                    if (_checkWhitelist(
                            defaultAccountWhitelist, 
                            defaultCodehashWhitelist, 
                            defaultWhitelistExtensions, 
                            collection, 
                            account)) {
                        return true;
                    }
                }
                (
                    mapping (address => bool) storage accountWhitelist,
                    mapping (bytes32 => bool) storage codehashWhitelist,
                    EnumerableSet.AddressSet storage whitelistExtensions
                ) = _get7702DelegateWhitelistStoragePointers(listId);
                return _checkWhitelist(accountWhitelist, codehashWhitelist, whitelistExtensions, collection, account);
            }
            function _checkWhitelist(
                mapping (address => bool) storage accountWhitelist,
                mapping (bytes32 => bool) storage codehashWhitelist,
                EnumerableSet.AddressSet storage whitelistExtensions,
                address collection,
                address account
            ) internal view returns (bool isWhitelisted) {
                isWhitelisted = 
                        accountWhitelist[account] || 
                        codehashWhitelist[_getCodeHashAsm(account)];
                if (!isWhitelisted) {
                    uint256 lengthOfExtensionsList = whitelistExtensions.length();
                    for (uint256 i = 0; i < lengthOfExtensionsList; ++i) {
                        IWhitelistExtension whitelistExtension = IWhitelistExtension(whitelistExtensions.at(i));
                        if (whitelistExtension.isWhitelisted(collection, account)) {
                            isWhitelisted = true;
                            break;
                        }
                    }
                }
            }
            function _checkWhitelist(
                mapping (address => bool) storage accountWhitelist,
                mapping (bytes32 => bool) storage codehashWhitelist,
                EnumerableSet.AddressSet storage whitelistExtensions,
                address collection,
                address account1,
                address account2
            ) internal view returns (bool isWhitelisted) {
                isWhitelisted = 
                    accountWhitelist[account1] || 
                    accountWhitelist[account2] || 
                    codehashWhitelist[_getCodeHashAsm(account1)] ||
                    codehashWhitelist[_getCodeHashAsm(account2)];
                if (!isWhitelisted) {
                    uint256 lengthOfExtensionsList = whitelistExtensions.length();
                    for (uint256 i = 0; i < lengthOfExtensionsList; ++i) {
                        IWhitelistExtension whitelistExtension = IWhitelistExtension(whitelistExtensions.at(i));
                        if (whitelistExtension.isWhitelisted(collection, account1) || 
                            whitelistExtension.isWhitelisted(collection, account2)) {
                            isWhitelisted = true;
                            break;
                        }
                    }
                }
            }
            /// @notice Returns true if the specified account has verified a signature on the registry, false otherwise.
            function _isVerifiedEOA(address account) internal view returns (bool) {
                return IEOARegistry(_eoaRegistry).isVerifiedEOA(account);
            }
            function _getOptions(uint8 globalOptions, uint16 whitelistOptions) internal pure returns (Options memory options) {
                options.enableAccountFreezingMode = 
                    _isFlagSet(globalOptions, FLAG_GLOBAL_ENABLE_ACCOUNT_FREEZING_MODE);
                options.disableAuthorizationMode = 
                    _isFlagSet(globalOptions, FLAG_GLOBAL_DISABLE_AUTHORIZATION_MODE);
                options.customListSupplementsDefaultList = 
                    _isFlagSet(globalOptions, FLAG_GLOBAL_CUSTOM_LIST_SUPPLEMENTS_DEFAULT_LIST);
                options.blockSmartWalletReceivers = 
                    _isFlagSet(whitelistOptions, FLAG_RULESET_WHITELIST_BLOCK_SMART_WALLET_RECEIVERS);
                options.blockUnverifiedEOAReceivers = 
                    _isFlagSet(whitelistOptions, FLAG_RULESET_WHITELIST_BLOCK_UNVERIFIED_EOA_RECEIVERS);
                options.blockAllOTC = 
                    _isFlagSet(whitelistOptions, FLAG_RULESET_WHITELIST_BLOCK_ALL_OTC);
                options.allowOTCFor7702Delegates = 
                    _isFlagSet(whitelistOptions, FLAG_RULESET_WHITELIST_ALLOW_OTC_FOR_7702_DELEGATES);
                    
                options.allowOTCForSmartWallets = 
                    _isFlagSet(whitelistOptions, FLAG_RULESET_WHITELIST_ALLOW_OTC_FOR_SMART_WALLETS);
            }
            function _getWhitelistStoragePointers(uint48 listId) 
            internal 
            view 
            returns (
                mapping (address => bool) storage accountWhitelist,
                mapping (bytes32 => bool) storage codehashWhitelist,
                EnumerableSet.AddressSet storage whitelistExtensions
            ) {
                List storage whitelist = validatorStorage().lists[LIST_TYPE_WHITELIST][listId];
                accountWhitelist = whitelist.nonEnumerableAccounts;
                codehashWhitelist = whitelist.nonEnumerableCodehashes;
                whitelistExtensions = 
                    validatorStorage().lists[EXPANSION_LIST_TYPE_WHITELIST_EXTENSION_CONTRACTS][listId].enumerableAccounts;
            }
            function _get7702DelegateWhitelistStoragePointers(uint48 listId) 
            internal 
            view 
            returns (
                mapping (address => bool) storage accountWhitelist,
                mapping (bytes32 => bool) storage codehashWhitelist,
                EnumerableSet.AddressSet storage whitelistExtensions
            ) {
                List storage whitelist = 
                    validatorStorage().lists[EXPANSION_LIST_TYPE_7702_DELEGATE_WHITELIST][listId];
                accountWhitelist = whitelist.nonEnumerableAccounts;
                codehashWhitelist = whitelist.nonEnumerableCodehashes;
                whitelistExtensions = 
                    validatorStorage().lists[EXPANSION_LIST_TYPE_7702_DELEGATE_WHITELIST_EXTENSION_CONTRACTS][listId].enumerableAccounts;
            }
        }