ETH Price: $2,510.32 (+0.13%)

Transaction Decoder

Block:
20641928 at Aug-30-2024 02:15:59 PM +UTC
Transaction Fee:
0.000509100588577412 ETH $1.28
Gas Used:
91,396 Gas / 5.570272097 Gwei

Emitted Events:

160 BasicERC721CWithBasicRoyalties.Transfer( from=[Sender] 0xe132f1cf7540b050ad4d34fc4d60ebed8f1da9b2, to=TransparentUpgradeableProxy, tokenId=49 )
161 TransparentUpgradeableProxy.0x5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62( 0x5548c837ab068cf56a2c2479df0882a4922fd203edb7517321831d95078c5f62, 0x0000000000000000000000001d72373852bbdad6c5ed20176d112e0a3f336160, 0x000000000000000000000000e132f1cf7540b050ad4d34fc4d60ebed8f1da9b2, 0000000000000000000000000000000000000000000000000000000000000031 )

Account State Difference:

  Address   Before After State Difference Code
0x0d3502C5...caC19F25c
0x1D723738...a3f336160
(beaverbuild)
11.036147018872340546 Eth11.036222678561953278 Eth0.000075659689612732
0xE132F1Cf...d8f1da9b2
2.856712738272197214 Eth
Nonce: 1596
2.856203637683619802 Eth
Nonce: 1597
0.000509100588577412

Execution Trace

BasicERC721CWithBasicRoyalties.safeTransferFrom( from=0xE132F1Cf7540B050ad4d34fc4D60EbEd8f1da9b2, to=0x0d3502C5ECEb9e3D643a068EEF36FA1caC19F25c, tokenId=49 )
  • CreatorTokenTransferValidator.applyCollectionTransferPolicy( caller=0xE132F1Cf7540B050ad4d34fc4D60EbEd8f1da9b2, from=0xE132F1Cf7540B050ad4d34fc4D60EbEd8f1da9b2, to=0x0d3502C5ECEb9e3D643a068EEF36FA1caC19F25c )
  • TransparentUpgradeableProxy.150b7a02( )
    • NftLock.onERC721Received( 0xE132F1Cf7540B050ad4d34fc4D60EbEd8f1da9b2, from=0xE132F1Cf7540B050ad4d34fc4D60EbEd8f1da9b2, tokenId=49, 0x )
      safeTransferFrom[ERC721 (ln:987)]
      File 1 of 4: BasicERC721CWithBasicRoyalties
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "./OwnablePermissions.sol";
      import "@openzeppelin/contracts/access/Ownable.sol";
      abstract contract OwnableBasic is OwnablePermissions, Ownable {
          function _requireCallerIsContractOwner() internal view virtual override {
              _checkOwner();
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "@openzeppelin/contracts/utils/Context.sol";
      abstract contract OwnablePermissions is Context {
          function _requireCallerIsContractOwner() internal view virtual;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../utils/CreatorTokenBase.sol";
      import "../token/erc721/ERC721OpenZeppelin.sol";
      /**
       * @title ERC721C
       * @author Limit Break, Inc.
       * @notice Extends OpenZeppelin's ERC721 implementation with Creator Token functionality, which
       *         allows the contract owner to update the transfer validation logic by managing a security policy in
       *         an external transfer validation security policy registry.  See {CreatorTokenTransferValidator}.
       */
      abstract contract ERC721C is ERC721OpenZeppelin, CreatorTokenBase {
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(ICreatorToken).interfaceId || super.supportsInterface(interfaceId);
          }
          /// @dev Ties the open-zeppelin _beforeTokenTransfer hook to more granular transfer validation logic
          function _beforeTokenTransfer(
              address from,
              address to,
              uint256 firstTokenId,
              uint256 batchSize) internal virtual override {
              for (uint256 i = 0; i < batchSize;) {
                  _validateBeforeTransfer(from, to, firstTokenId + i);
                  unchecked {
                      ++i;
                  }
              }
          }
          /// @dev Ties the open-zeppelin _afterTokenTransfer hook to more granular transfer validation logic
          function _afterTokenTransfer(
              address from,
              address to,
              uint256 firstTokenId,
              uint256 batchSize) internal virtual override {
              for (uint256 i = 0; i < batchSize;) {
                  _validateAfterTransfer(from, to, firstTokenId + i);
                  unchecked {
                      ++i;
                  }
              }
          }
      }
      /**
       * @title ERC721CInitializable
       * @author Limit Break, Inc.
       * @notice Initializable implementation of ERC721C to allow for EIP-1167 proxy clones.
       */
      abstract contract ERC721CInitializable is ERC721OpenZeppelinInitializable, CreatorTokenBase {
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(ICreatorToken).interfaceId || super.supportsInterface(interfaceId);
          }
          /// @dev Ties the open-zeppelin _beforeTokenTransfer hook to more granular transfer validation logic
          function _beforeTokenTransfer(
              address from,
              address to,
              uint256 firstTokenId,
              uint256 batchSize) internal virtual override {
              for (uint256 i = 0; i < batchSize;) {
                  _validateBeforeTransfer(from, to, firstTokenId + i);
                  unchecked {
                      ++i;
                  }
              }
          }
          /// @dev Ties the open-zeppelin _afterTokenTransfer hook to more granular transfer validation logic
          function _afterTokenTransfer(
              address from,
              address to,
              uint256 firstTokenId,
              uint256 batchSize) internal virtual override {
              for (uint256 i = 0; i < batchSize;) {
                  _validateAfterTransfer(from, to, firstTokenId + i);
                  unchecked {
                      ++i;
                  }
              }
          }
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../interfaces/ICreatorTokenTransferValidator.sol";
      interface ICreatorToken {
          event TransferValidatorUpdated(address oldValidator, address newValidator);
          function getTransferValidator() external view returns (ICreatorTokenTransferValidator);
          function getSecurityPolicy() external view returns (CollectionSecurityPolicy memory);
          function getWhitelistedOperators() external view returns (address[] memory);
          function getPermittedContractReceivers() external view returns (address[] memory);
          function isOperatorWhitelisted(address operator) external view returns (bool);
          function isContractReceiverPermitted(address receiver) external view returns (bool);
          function isTransferAllowed(address caller, address from, address to) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "./IEOARegistry.sol";
      import "./ITransferSecurityRegistry.sol";
      import "./ITransferValidator.sol";
      interface ICreatorTokenTransferValidator is ITransferSecurityRegistry, ITransferValidator, IEOARegistry {}// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
      interface IEOARegistry is IERC165 {
          function isVerifiedEOA(address account) external view returns (bool);
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../utils/TransferPolicy.sol";
      interface ITransferSecurityRegistry {
          event AddedToAllowlist(AllowlistTypes indexed kind, uint256 indexed id, address indexed account);
          event CreatedAllowlist(AllowlistTypes indexed kind, uint256 indexed id, string indexed name);
          event ReassignedAllowlistOwnership(AllowlistTypes indexed kind, uint256 indexed id, address indexed newOwner);
          event RemovedFromAllowlist(AllowlistTypes indexed kind, uint256 indexed id, address indexed account);
          event SetAllowlist(AllowlistTypes indexed kind, address indexed collection, uint120 indexed id);
          event SetTransferSecurityLevel(address indexed collection, TransferSecurityLevels level);
          function createOperatorWhitelist(string calldata name) external returns (uint120);
          function createPermittedContractReceiverAllowlist(string calldata name) external returns (uint120);
          function reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) external;
          function reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) external;
          function renounceOwnershipOfOperatorWhitelist(uint120 id) external;
          function renounceOwnershipOfPermittedContractReceiverAllowlist(uint120 id) external;
          function setTransferSecurityLevelOfCollection(address collection, TransferSecurityLevels level) external;
          function setOperatorWhitelistOfCollection(address collection, uint120 id) external;
          function setPermittedContractReceiverAllowlistOfCollection(address collection, uint120 id) external;
          function addOperatorToWhitelist(uint120 id, address operator) external;
          function addPermittedContractReceiverToAllowlist(uint120 id, address receiver) external;
          function removeOperatorFromWhitelist(uint120 id, address operator) external;
          function removePermittedContractReceiverFromAllowlist(uint120 id, address receiver) external;
          function getCollectionSecurityPolicy(address collection) external view returns (CollectionSecurityPolicy memory);
          function getWhitelistedOperators(uint120 id) external view returns (address[] memory);
          function getPermittedContractReceivers(uint120 id) external view returns (address[] memory);
          function isOperatorWhitelisted(uint120 id, address operator) external view returns (bool);
          function isContractReceiverPermitted(uint120 id, address receiver) external view returns (bool);
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../utils/TransferPolicy.sol";
      interface ITransferValidator {
          function applyCollectionTransferPolicy(address caller, address from, address to) external view;
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "@openzeppelin/contracts/token/common/ERC2981.sol";
      /**
       * @title BasicRoyaltiesBase
       * @author Limit Break, Inc.
       * @dev Base functionality of an NFT mix-in contract implementing the most basic form of programmable royalties.
       */
      abstract contract BasicRoyaltiesBase is ERC2981 {
          event DefaultRoyaltySet(address indexed receiver, uint96 feeNumerator);
          event TokenRoyaltySet(uint256 indexed tokenId, address indexed receiver, uint96 feeNumerator);
          function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual override {
              super._setDefaultRoyalty(receiver, feeNumerator);
              emit DefaultRoyaltySet(receiver, feeNumerator);
          }
          function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual override {
              super._setTokenRoyalty(tokenId, receiver, feeNumerator);
              emit TokenRoyaltySet(tokenId, receiver, feeNumerator);
          }
      }
      /**
       * @title BasicRoyalties
       * @author Limit Break, Inc.
       * @notice Constructable BasicRoyalties Contract implementation.
       */
      abstract contract BasicRoyalties is BasicRoyaltiesBase {
          constructor(address receiver, uint96 feeNumerator) {
              _setDefaultRoyalty(receiver, feeNumerator);
          }
      }
      /**
       * @title BasicRoyaltiesInitializable
       * @author Limit Break, Inc.
       * @notice Initializable BasicRoyalties Contract implementation to allow for EIP-1167 clones. 
       */
      abstract contract BasicRoyaltiesInitializable is BasicRoyaltiesBase {}// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../../access/OwnablePermissions.sol";
      import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
      abstract contract ERC721OpenZeppelinBase is ERC721 {
          // Token name
          string internal _contractName;
          // Token symbol
          string internal _contractSymbol;
          function name() public view virtual override returns (string memory) {
              return _contractName;
          }
          function symbol() public view virtual override returns (string memory) {
              return _contractSymbol;
          }
          function _setNameAndSymbol(string memory name_, string memory symbol_) internal {
              _contractName = name_;
              _contractSymbol = symbol_;
          }
      }
      abstract contract ERC721OpenZeppelin is ERC721OpenZeppelinBase {
          constructor(string memory name_, string memory symbol_) ERC721("", "") {
              _setNameAndSymbol(name_, symbol_);
          }
      }
      abstract contract ERC721OpenZeppelinInitializable is OwnablePermissions, ERC721OpenZeppelinBase {
          error ERC721OpenZeppelinInitializable__AlreadyInitializedERC721();
          /// @notice Specifies whether or not the contract is initialized
          bool private _erc721Initialized;
          /// @dev Initializes parameters of ERC721 tokens.
          /// These cannot be set in the constructor because this contract is optionally compatible with EIP-1167.
          function initializeERC721(string memory name_, string memory symbol_) public {
              _requireCallerIsContractOwner();
              if(_erc721Initialized) {
                  revert ERC721OpenZeppelinInitializable__AlreadyInitializedERC721();
              }
              _erc721Initialized = true;
              _setNameAndSymbol(name_, symbol_);
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../access/OwnablePermissions.sol";
      import "../interfaces/ICreatorToken.sol";
      import "../interfaces/ICreatorTokenTransferValidator.sol";
      import "../utils/TransferValidation.sol";
      import "@openzeppelin/contracts/interfaces/IERC165.sol";
      /**
       * @title CreatorTokenBase
       * @author Limit Break, Inc.
       * @notice CreatorTokenBase is an abstract contract that provides basic functionality for managing token 
       * transfer policies through an implementation of ICreatorTokenTransferValidator. This contract is intended to be used
       * as a base for creator-specific token contracts, enabling customizable transfer restrictions and security policies.
       *
       * <h4>Features:</h4>
       * <ul>Ownable: This contract can have an owner who can set and update the transfer validator.</ul>
       * <ul>TransferValidation: Implements the basic token transfer validation interface.</ul>
       * <ul>ICreatorToken: Implements the interface for creator tokens, providing view functions for token security policies.</ul>
       *
       * <h4>Benefits:</h4>
       * <ul>Provides a flexible and modular way to implement custom token transfer restrictions and security policies.</ul>
       * <ul>Allows creators to enforce policies such as whitelisted operators and permitted contract receivers.</ul>
       * <ul>Can be easily integrated into other token contracts as a base contract.</ul>
       *
       * <h4>Intended Usage:</h4>
       * <ul>Use as a base contract for creator token implementations that require advanced transfer restrictions and 
       *   security policies.</ul>
       * <ul>Set and update the ICreatorTokenTransferValidator implementation contract to enforce desired policies for the 
       *   creator token.</ul>
       */
      abstract contract CreatorTokenBase is OwnablePermissions, TransferValidation, ICreatorToken {
          
          error CreatorTokenBase__InvalidTransferValidatorContract();
          error CreatorTokenBase__SetTransferValidatorFirst();
          address public constant DEFAULT_TRANSFER_VALIDATOR = address(0x0000721C310194CcfC01E523fc93C9cCcFa2A0Ac);
          TransferSecurityLevels public constant DEFAULT_TRANSFER_SECURITY_LEVEL = TransferSecurityLevels.One;
          uint120 public constant DEFAULT_OPERATOR_WHITELIST_ID = uint120(1);
          ICreatorTokenTransferValidator private transferValidator;
          /**
           * @notice Allows the contract owner to set the transfer validator to the official validator contract
           *         and set the security policy to the recommended default settings.
           * @dev    May be overridden to change the default behavior of an individual collection.
           */
          function setToDefaultSecurityPolicy() public virtual {
              _requireCallerIsContractOwner();
              setTransferValidator(DEFAULT_TRANSFER_VALIDATOR);
              ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR).setTransferSecurityLevelOfCollection(address(this), DEFAULT_TRANSFER_SECURITY_LEVEL);
              ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR).setOperatorWhitelistOfCollection(address(this), DEFAULT_OPERATOR_WHITELIST_ID);
          }
          /**
           * @notice Allows the contract owner to set the transfer validator to a custom validator contract
           *         and set the security policy to their own custom settings.
           */
          function setToCustomValidatorAndSecurityPolicy(
              address validator, 
              TransferSecurityLevels level, 
              uint120 operatorWhitelistId, 
              uint120 permittedContractReceiversAllowlistId) public {
              _requireCallerIsContractOwner();
              setTransferValidator(validator);
              ICreatorTokenTransferValidator(validator).
                  setTransferSecurityLevelOfCollection(address(this), level);
              ICreatorTokenTransferValidator(validator).
                  setOperatorWhitelistOfCollection(address(this), operatorWhitelistId);
              ICreatorTokenTransferValidator(validator).
                  setPermittedContractReceiverAllowlistOfCollection(address(this), permittedContractReceiversAllowlistId);
          }
          /**
           * @notice Allows the contract owner to set the security policy to their own custom settings.
           * @dev    Reverts if the transfer validator has not been set.
           */
          function setToCustomSecurityPolicy(
              TransferSecurityLevels level, 
              uint120 operatorWhitelistId, 
              uint120 permittedContractReceiversAllowlistId) public {
              _requireCallerIsContractOwner();
              ICreatorTokenTransferValidator validator = getTransferValidator();
              if (address(validator) == address(0)) {
                  revert CreatorTokenBase__SetTransferValidatorFirst();
              }
              validator.setTransferSecurityLevelOfCollection(address(this), level);
              validator.setOperatorWhitelistOfCollection(address(this), operatorWhitelistId);
              validator.setPermittedContractReceiverAllowlistOfCollection(address(this), permittedContractReceiversAllowlistId);
          }
          /**
           * @notice Sets the transfer validator for the token contract.
           *
           * @dev    Throws when provided validator contract is not the zero address and doesn't support 
           *         the ICreatorTokenTransferValidator interface. 
           * @dev    Throws when the caller is not the contract owner.
           *
           * @dev    <h4>Postconditions:</h4>
           *         1. The transferValidator address is updated.
           *         2. The `TransferValidatorUpdated` event is emitted.
           *
           * @param transferValidator_ The address of the transfer validator contract.
           */
          function setTransferValidator(address transferValidator_) public {
              _requireCallerIsContractOwner();
              bool isValidTransferValidator = false;
              if(transferValidator_.code.length > 0) {
                  try IERC165(transferValidator_).supportsInterface(type(ICreatorTokenTransferValidator).interfaceId) 
                      returns (bool supportsInterface) {
                      isValidTransferValidator = supportsInterface;
                  } catch {}
              }
              if(transferValidator_ != address(0) && !isValidTransferValidator) {
                  revert CreatorTokenBase__InvalidTransferValidatorContract();
              }
              emit TransferValidatorUpdated(address(transferValidator), transferValidator_);
              transferValidator = ICreatorTokenTransferValidator(transferValidator_);
          }
          /**
           * @notice Returns the transfer validator contract address for this token contract.
           */
          function getTransferValidator() public view override returns (ICreatorTokenTransferValidator) {
              return transferValidator;
          }
          /**
           * @notice Returns the security policy for this token contract, which includes:
           *         Transfer security level, operator whitelist id, permitted contract receiver allowlist id.
           */
          function getSecurityPolicy() public view override returns (CollectionSecurityPolicy memory) {
              if (address(transferValidator) != address(0)) {
                  return transferValidator.getCollectionSecurityPolicy(address(this));
              }
              return CollectionSecurityPolicy({
                  transferSecurityLevel: TransferSecurityLevels.Zero,
                  operatorWhitelistId: 0,
                  permittedContractReceiversId: 0
              });
          }
          /**
           * @notice Returns the list of all whitelisted operators for this token contract.
           * @dev    This can be an expensive call and should only be used in view-only functions.
           */
          function getWhitelistedOperators() public view override returns (address[] memory) {
              if (address(transferValidator) != address(0)) {
                  return transferValidator.getWhitelistedOperators(
                      transferValidator.getCollectionSecurityPolicy(address(this)).operatorWhitelistId);
              }
              return new address[](0);
          }
          /**
           * @notice Returns the list of permitted contract receivers for this token contract.
           * @dev    This can be an expensive call and should only be used in view-only functions.
           */
          function getPermittedContractReceivers() public view override returns (address[] memory) {
              if (address(transferValidator) != address(0)) {
                  return transferValidator.getPermittedContractReceivers(
                      transferValidator.getCollectionSecurityPolicy(address(this)).permittedContractReceiversId);
              }
              return new address[](0);
          }
          /**
           * @notice Checks if an operator is whitelisted for this token contract.
           * @param operator The address of the operator to check.
           */
          function isOperatorWhitelisted(address operator) public view override returns (bool) {
              if (address(transferValidator) != address(0)) {
                  return transferValidator.isOperatorWhitelisted(
                      transferValidator.getCollectionSecurityPolicy(address(this)).operatorWhitelistId, operator);
              }
              return false;
          }
          /**
           * @notice Checks if a contract receiver is permitted for this token contract.
           * @param receiver The address of the receiver to check.
           */
          function isContractReceiverPermitted(address receiver) public view override returns (bool) {
              if (address(transferValidator) != address(0)) {
                  return transferValidator.isContractReceiverPermitted(
                      transferValidator.getCollectionSecurityPolicy(address(this)).permittedContractReceiversId, receiver);
              }
              return false;
          }
          /**
           * @notice Determines if a transfer is allowed based on the token contract's security policy.  Use this function
           *         to simulate whether or not a transfer made by the specified `caller` from the `from` address to the `to`
           *         address would be allowed by this token's security policy.
           *
           * @notice This function only checks the security policy restrictions and does not check whether token ownership
           *         or approvals are in place. 
           *
           * @param caller The address of the simulated caller.
           * @param from   The address of the sender.
           * @param to     The address of the receiver.
           * @return       True if the transfer is allowed, false otherwise.
           */
          function isTransferAllowed(address caller, address from, address to) public view override returns (bool) {
              if (address(transferValidator) != address(0)) {
                  try transferValidator.applyCollectionTransferPolicy(caller, from, to) {
                      return true;
                  } catch {
                      return false;
                  }
              }
              return true;
          }
          /**
           * @dev Pre-validates a token transfer, reverting if the transfer is not allowed by this token's security policy.
           *      Inheriting contracts are responsible for overriding the _beforeTokenTransfer function, or its equivalent
           *      and calling _validateBeforeTransfer so that checks can be properly applied during token transfers.
           *
           * @dev Throws when the transfer doesn't comply with the collection's transfer policy, if the transferValidator is
           *      set to a non-zero address.
           *
           * @param caller  The address of the caller.
           * @param from    The address of the sender.
           * @param to      The address of the receiver.
           */
          function _preValidateTransfer(
              address caller, 
              address from, 
              address to, 
              uint256 /*tokenId*/, 
              uint256 /*value*/) internal virtual override {
              if (address(transferValidator) != address(0)) {
                  transferValidator.applyCollectionTransferPolicy(caller, from, to);
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      enum AllowlistTypes {
          Operators,
          PermittedContractReceivers
      }
      enum ReceiverConstraints {
          None,
          NoCode,
          EOA
      }
      enum CallerConstraints {
          None,
          OperatorWhitelistEnableOTC,
          OperatorWhitelistDisableOTC
      }
      enum StakerConstraints {
          None,
          CallerIsTxOrigin,
          EOA
      }
      enum TransferSecurityLevels {
          Zero,
          One,
          Two,
          Three,
          Four,
          Five,
          Six
      }
      struct TransferSecurityPolicy {
          CallerConstraints callerConstraints;
          ReceiverConstraints receiverConstraints;
      }
      struct CollectionSecurityPolicy {
          TransferSecurityLevels transferSecurityLevel;
          uint120 operatorWhitelistId;
          uint120 permittedContractReceiversId;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "@openzeppelin/contracts/utils/Context.sol";
      /**
       * @title TransferValidation
       * @author Limit Break, Inc.
       * @notice A mix-in that can be combined with ERC-721 contracts to provide more granular hooks.
       * Openzeppelin's ERC721 contract only provides hooks for before and after transfer.  This allows
       * developers to validate or customize transfers within the context of a mint, a burn, or a transfer.
       */
      abstract contract TransferValidation is Context {
          
          error ShouldNotMintToBurnAddress();
          /// @dev Inheriting contracts should call this function in the _beforeTokenTransfer function to get more granular hooks.
          function _validateBeforeTransfer(address from, address to, uint256 tokenId) internal virtual {
              bool fromZeroAddress = from == address(0);
              bool toZeroAddress = to == address(0);
              if(fromZeroAddress && toZeroAddress) {
                  revert ShouldNotMintToBurnAddress();
              } else if(fromZeroAddress) {
                  _preValidateMint(_msgSender(), to, tokenId, msg.value);
              } else if(toZeroAddress) {
                  _preValidateBurn(_msgSender(), from, tokenId, msg.value);
              } else {
                  _preValidateTransfer(_msgSender(), from, to, tokenId, msg.value);
              }
          }
          /// @dev Inheriting contracts should call this function in the _afterTokenTransfer function to get more granular hooks.
          function _validateAfterTransfer(address from, address to, uint256 tokenId) internal virtual {
              bool fromZeroAddress = from == address(0);
              bool toZeroAddress = to == address(0);
              if(fromZeroAddress && toZeroAddress) {
                  revert ShouldNotMintToBurnAddress();
              } else if(fromZeroAddress) {
                  _postValidateMint(_msgSender(), to, tokenId, msg.value);
              } else if(toZeroAddress) {
                  _postValidateBurn(_msgSender(), from, tokenId, msg.value);
              } else {
                  _postValidateTransfer(_msgSender(), from, to, tokenId, msg.value);
              }
          }
          /// @dev Optional validation hook that fires before a mint
          function _preValidateMint(address caller, address to, uint256 tokenId, uint256 value) internal virtual {}
          /// @dev Optional validation hook that fires after a mint
          function _postValidateMint(address caller, address to, uint256 tokenId, uint256 value) internal virtual {}
          /// @dev Optional validation hook that fires before a burn
          function _preValidateBurn(address caller, address from, uint256 tokenId, uint256 value) internal virtual {}
          /// @dev Optional validation hook that fires after a burn
          function _postValidateBurn(address caller, address from, uint256 tokenId, uint256 value) internal virtual {}
          /// @dev Optional validation hook that fires before a transfer
          function _preValidateTransfer(address caller, address from, address to, uint256 tokenId, uint256 value) internal virtual {}
          /// @dev Optional validation hook that fires after a transfer
          function _postValidateTransfer(address caller, address from, address to, uint256 tokenId, uint256 value) internal virtual {}
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
      pragma solidity ^0.8.0;
      import "../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 {
          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 {
              require(owner() == _msgSender(), "Ownable: caller is not the owner");
          }
          /**
           * @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 {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              _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);
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol)
      pragma solidity ^0.8.0;
      import "../utils/introspection/IERC165.sol";
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol)
      pragma solidity ^0.8.0;
      import "../utils/introspection/IERC165.sol";
      /**
       * @dev Interface for the NFT Royalty Standard.
       *
       * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
       * support for royalty payments across all NFT marketplaces and ecosystem participants.
       *
       * _Available since v4.5._
       */
      interface IERC2981 is IERC165 {
          /**
           * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
           * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
           */
          function royaltyInfo(
              uint256 tokenId,
              uint256 salePrice
          ) external view returns (address receiver, uint256 royaltyAmount);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.9.3) (metatx/ERC2771Context.sol)
      pragma solidity ^0.8.9;
      import "../utils/Context.sol";
      /**
       * @dev Context variant with ERC2771 support.
       */
      abstract contract ERC2771Context is Context {
          /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
          address private immutable _trustedForwarder;
          /// @custom:oz-upgrades-unsafe-allow constructor
          constructor(address trustedForwarder) {
              _trustedForwarder = trustedForwarder;
          }
          function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
              return forwarder == _trustedForwarder;
          }
          function _msgSender() internal view virtual override returns (address sender) {
              if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) {
                  // The assembly code is more direct than the Solidity version using `abi.decode`.
                  /// @solidity memory-safe-assembly
                  assembly {
                      sender := shr(96, calldataload(sub(calldatasize(), 20)))
                  }
              } else {
                  return super._msgSender();
              }
          }
          function _msgData() internal view virtual override returns (bytes calldata) {
              if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) {
                  return msg.data[:msg.data.length - 20];
              } else {
                  return super._msgData();
              }
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
      pragma solidity ^0.8.0;
      import "../utils/Context.sol";
      /**
       * @dev Contract module which allows children to implement an emergency stop
       * mechanism that can be triggered by an authorized account.
       *
       * This module is used through inheritance. It will make available the
       * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
       * the functions of your contract. Note that they will not be pausable by
       * simply including this module, only once the modifiers are put in place.
       */
      abstract contract Pausable is Context {
          /**
           * @dev Emitted when the pause is triggered by `account`.
           */
          event Paused(address account);
          /**
           * @dev Emitted when the pause is lifted by `account`.
           */
          event Unpaused(address account);
          bool private _paused;
          /**
           * @dev Initializes the contract in unpaused state.
           */
          constructor() {
              _paused = false;
          }
          /**
           * @dev Modifier to make a function callable only when the contract is not paused.
           *
           * Requirements:
           *
           * - The contract must not be paused.
           */
          modifier whenNotPaused() {
              _requireNotPaused();
              _;
          }
          /**
           * @dev Modifier to make a function callable only when the contract is paused.
           *
           * Requirements:
           *
           * - The contract must be paused.
           */
          modifier whenPaused() {
              _requirePaused();
              _;
          }
          /**
           * @dev Returns true if the contract is paused, and false otherwise.
           */
          function paused() public view virtual returns (bool) {
              return _paused;
          }
          /**
           * @dev Throws if the contract is paused.
           */
          function _requireNotPaused() internal view virtual {
              require(!paused(), "Pausable: paused");
          }
          /**
           * @dev Throws if the contract is not paused.
           */
          function _requirePaused() internal view virtual {
              require(paused(), "Pausable: not paused");
          }
          /**
           * @dev Triggers stopped state.
           *
           * Requirements:
           *
           * - The contract must not be paused.
           */
          function _pause() internal virtual whenNotPaused {
              _paused = true;
              emit Paused(_msgSender());
          }
          /**
           * @dev Returns to normal state.
           *
           * Requirements:
           *
           * - The contract must be paused.
           */
          function _unpause() internal virtual whenPaused {
              _paused = false;
              emit Unpaused(_msgSender());
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.9.0) (token/common/ERC2981.sol)
      pragma solidity ^0.8.0;
      import "../../interfaces/IERC2981.sol";
      import "../../utils/introspection/ERC165.sol";
      /**
       * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
       *
       * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
       * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
       *
       * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
       * fee is specified in basis points by default.
       *
       * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
       * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
       * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
       *
       * _Available since v4.5._
       */
      abstract contract ERC2981 is IERC2981, ERC165 {
          struct RoyaltyInfo {
              address receiver;
              uint96 royaltyFraction;
          }
          RoyaltyInfo private _defaultRoyaltyInfo;
          mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
              return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
          }
          /**
           * @inheritdoc IERC2981
           */
          function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual override returns (address, uint256) {
              RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId];
              if (royalty.receiver == address(0)) {
                  royalty = _defaultRoyaltyInfo;
              }
              uint256 royaltyAmount = (salePrice * royalty.royaltyFraction) / _feeDenominator();
              return (royalty.receiver, royaltyAmount);
          }
          /**
           * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
           * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
           * override.
           */
          function _feeDenominator() internal pure virtual returns (uint96) {
              return 10000;
          }
          /**
           * @dev Sets the royalty information that all ids in this contract will default to.
           *
           * Requirements:
           *
           * - `receiver` cannot be the zero address.
           * - `feeNumerator` cannot be greater than the fee denominator.
           */
          function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
              require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
              require(receiver != address(0), "ERC2981: invalid receiver");
              _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
          }
          /**
           * @dev Removes default royalty information.
           */
          function _deleteDefaultRoyalty() internal virtual {
              delete _defaultRoyaltyInfo;
          }
          /**
           * @dev Sets the royalty information for a specific token id, overriding the global default.
           *
           * Requirements:
           *
           * - `receiver` cannot be the zero address.
           * - `feeNumerator` cannot be greater than the fee denominator.
           */
          function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual {
              require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
              require(receiver != address(0), "ERC2981: Invalid parameters");
              _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
          }
          /**
           * @dev Resets royalty information for the token id back to the global default.
           */
          function _resetTokenRoyalty(uint256 tokenId) internal virtual {
              delete _tokenRoyaltyInfo[tokenId];
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
      pragma solidity ^0.8.0;
      import "./IERC721.sol";
      import "./IERC721Receiver.sol";
      import "./extensions/IERC721Metadata.sol";
      import "../../utils/Address.sol";
      import "../../utils/Context.sol";
      import "../../utils/Strings.sol";
      import "../../utils/introspection/ERC165.sol";
      /**
       * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
       * the Metadata extension, but not including the Enumerable extension, which is available separately as
       * {ERC721Enumerable}.
       */
      contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
          using Address for address;
          using Strings for uint256;
          // Token name
          string private _name;
          // Token symbol
          string private _symbol;
          // Mapping from token ID to owner address
          mapping(uint256 => address) private _owners;
          // Mapping owner address to token count
          mapping(address => uint256) private _balances;
          // Mapping from token ID to approved address
          mapping(uint256 => address) private _tokenApprovals;
          // Mapping from owner to operator approvals
          mapping(address => mapping(address => bool)) private _operatorApprovals;
          /**
           * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
           */
          constructor(string memory name_, string memory symbol_) {
              _name = name_;
              _symbol = symbol_;
          }
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
              return
                  interfaceId == type(IERC721).interfaceId ||
                  interfaceId == type(IERC721Metadata).interfaceId ||
                  super.supportsInterface(interfaceId);
          }
          /**
           * @dev See {IERC721-balanceOf}.
           */
          function balanceOf(address owner) public view virtual override returns (uint256) {
              require(owner != address(0), "ERC721: address zero is not a valid owner");
              return _balances[owner];
          }
          /**
           * @dev See {IERC721-ownerOf}.
           */
          function ownerOf(uint256 tokenId) public view virtual override returns (address) {
              address owner = _ownerOf(tokenId);
              require(owner != address(0), "ERC721: invalid token ID");
              return owner;
          }
          /**
           * @dev See {IERC721Metadata-name}.
           */
          function name() public view virtual override returns (string memory) {
              return _name;
          }
          /**
           * @dev See {IERC721Metadata-symbol}.
           */
          function symbol() public view virtual override returns (string memory) {
              return _symbol;
          }
          /**
           * @dev See {IERC721Metadata-tokenURI}.
           */
          function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
              _requireMinted(tokenId);
              string memory baseURI = _baseURI();
              return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
          }
          /**
           * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
           * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
           * by default, can be overridden in child contracts.
           */
          function _baseURI() internal view virtual returns (string memory) {
              return "";
          }
          /**
           * @dev See {IERC721-approve}.
           */
          function approve(address to, uint256 tokenId) public virtual override {
              address owner = ERC721.ownerOf(tokenId);
              require(to != owner, "ERC721: approval to current owner");
              require(
                  _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                  "ERC721: approve caller is not token owner or approved for all"
              );
              _approve(to, tokenId);
          }
          /**
           * @dev See {IERC721-getApproved}.
           */
          function getApproved(uint256 tokenId) public view virtual override returns (address) {
              _requireMinted(tokenId);
              return _tokenApprovals[tokenId];
          }
          /**
           * @dev See {IERC721-setApprovalForAll}.
           */
          function setApprovalForAll(address operator, bool approved) public virtual override {
              _setApprovalForAll(_msgSender(), operator, approved);
          }
          /**
           * @dev See {IERC721-isApprovedForAll}.
           */
          function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
              return _operatorApprovals[owner][operator];
          }
          /**
           * @dev See {IERC721-transferFrom}.
           */
          function transferFrom(address from, address to, uint256 tokenId) public virtual override {
              //solhint-disable-next-line max-line-length
              require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
              _transfer(from, to, tokenId);
          }
          /**
           * @dev See {IERC721-safeTransferFrom}.
           */
          function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
              safeTransferFrom(from, to, tokenId, "");
          }
          /**
           * @dev See {IERC721-safeTransferFrom}.
           */
          function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
              require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
              _safeTransfer(from, to, tokenId, data);
          }
          /**
           * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
           * are aware of the ERC721 protocol to prevent tokens from being forever locked.
           *
           * `data` is additional data, it has no specified format and it is sent in call to `to`.
           *
           * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
           * implement alternative mechanisms to perform token transfer, such as signature-based.
           *
           * Requirements:
           *
           * - `from` cannot be the zero address.
           * - `to` cannot be the zero address.
           * - `tokenId` token must exist and be owned by `from`.
           * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
           *
           * Emits a {Transfer} event.
           */
          function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
              _transfer(from, to, tokenId);
              require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
          }
          /**
           * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
           */
          function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
              return _owners[tokenId];
          }
          /**
           * @dev Returns whether `tokenId` exists.
           *
           * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
           *
           * Tokens start existing when they are minted (`_mint`),
           * and stop existing when they are burned (`_burn`).
           */
          function _exists(uint256 tokenId) internal view virtual returns (bool) {
              return _ownerOf(tokenId) != address(0);
          }
          /**
           * @dev Returns whether `spender` is allowed to manage `tokenId`.
           *
           * Requirements:
           *
           * - `tokenId` must exist.
           */
          function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
              address owner = ERC721.ownerOf(tokenId);
              return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
          }
          /**
           * @dev Safely mints `tokenId` and transfers it to `to`.
           *
           * Requirements:
           *
           * - `tokenId` must not exist.
           * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
           *
           * Emits a {Transfer} event.
           */
          function _safeMint(address to, uint256 tokenId) internal virtual {
              _safeMint(to, tokenId, "");
          }
          /**
           * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
           * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
           */
          function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
              _mint(to, tokenId);
              require(
                  _checkOnERC721Received(address(0), to, tokenId, data),
                  "ERC721: transfer to non ERC721Receiver implementer"
              );
          }
          /**
           * @dev Mints `tokenId` and transfers it to `to`.
           *
           * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
           *
           * Requirements:
           *
           * - `tokenId` must not exist.
           * - `to` cannot be the zero address.
           *
           * Emits a {Transfer} event.
           */
          function _mint(address to, uint256 tokenId) internal virtual {
              require(to != address(0), "ERC721: mint to the zero address");
              require(!_exists(tokenId), "ERC721: token already minted");
              _beforeTokenTransfer(address(0), to, tokenId, 1);
              // Check that tokenId was not minted by `_beforeTokenTransfer` hook
              require(!_exists(tokenId), "ERC721: token already minted");
              unchecked {
                  // Will not overflow unless all 2**256 token ids are minted to the same owner.
                  // Given that tokens are minted one by one, it is impossible in practice that
                  // this ever happens. Might change if we allow batch minting.
                  // The ERC fails to describe this case.
                  _balances[to] += 1;
              }
              _owners[tokenId] = to;
              emit Transfer(address(0), to, tokenId);
              _afterTokenTransfer(address(0), to, tokenId, 1);
          }
          /**
           * @dev Destroys `tokenId`.
           * The approval is cleared when the token is burned.
           * This is an internal function that does not check if the sender is authorized to operate on the token.
           *
           * Requirements:
           *
           * - `tokenId` must exist.
           *
           * Emits a {Transfer} event.
           */
          function _burn(uint256 tokenId) internal virtual {
              address owner = ERC721.ownerOf(tokenId);
              _beforeTokenTransfer(owner, address(0), tokenId, 1);
              // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
              owner = ERC721.ownerOf(tokenId);
              // Clear approvals
              delete _tokenApprovals[tokenId];
              unchecked {
                  // Cannot overflow, as that would require more tokens to be burned/transferred
                  // out than the owner initially received through minting and transferring in.
                  _balances[owner] -= 1;
              }
              delete _owners[tokenId];
              emit Transfer(owner, address(0), tokenId);
              _afterTokenTransfer(owner, address(0), tokenId, 1);
          }
          /**
           * @dev Transfers `tokenId` from `from` to `to`.
           *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
           *
           * Requirements:
           *
           * - `to` cannot be the zero address.
           * - `tokenId` token must be owned by `from`.
           *
           * Emits a {Transfer} event.
           */
          function _transfer(address from, address to, uint256 tokenId) internal virtual {
              require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
              require(to != address(0), "ERC721: transfer to the zero address");
              _beforeTokenTransfer(from, to, tokenId, 1);
              // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
              require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
              // Clear approvals from the previous owner
              delete _tokenApprovals[tokenId];
              unchecked {
                  // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
                  // `from`'s balance is the number of token held, which is at least one before the current
                  // transfer.
                  // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
                  // all 2**256 token ids to be minted, which in practice is impossible.
                  _balances[from] -= 1;
                  _balances[to] += 1;
              }
              _owners[tokenId] = to;
              emit Transfer(from, to, tokenId);
              _afterTokenTransfer(from, to, tokenId, 1);
          }
          /**
           * @dev Approve `to` to operate on `tokenId`
           *
           * Emits an {Approval} event.
           */
          function _approve(address to, uint256 tokenId) internal virtual {
              _tokenApprovals[tokenId] = to;
              emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
          }
          /**
           * @dev Approve `operator` to operate on all of `owner` tokens
           *
           * Emits an {ApprovalForAll} event.
           */
          function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
              require(owner != operator, "ERC721: approve to caller");
              _operatorApprovals[owner][operator] = approved;
              emit ApprovalForAll(owner, operator, approved);
          }
          /**
           * @dev Reverts if the `tokenId` has not been minted yet.
           */
          function _requireMinted(uint256 tokenId) internal view virtual {
              require(_exists(tokenId), "ERC721: invalid token ID");
          }
          /**
           * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
           * The call is not executed if the target address is not a contract.
           *
           * @param from address representing the previous owner of the given token ID
           * @param to target address that will receive the tokens
           * @param tokenId uint256 ID of the token to be transferred
           * @param data bytes optional data to send along with the call
           * @return bool whether the call correctly returned the expected magic value
           */
          function _checkOnERC721Received(
              address from,
              address to,
              uint256 tokenId,
              bytes memory data
          ) private returns (bool) {
              if (to.isContract()) {
                  try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                      return retval == IERC721Receiver.onERC721Received.selector;
                  } catch (bytes memory reason) {
                      if (reason.length == 0) {
                          revert("ERC721: transfer to non ERC721Receiver implementer");
                      } else {
                          /// @solidity memory-safe-assembly
                          assembly {
                              revert(add(32, reason), mload(reason))
                          }
                      }
                  }
              } else {
                  return true;
              }
          }
          /**
           * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
           * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
           *
           * Calling conditions:
           *
           * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
           * - When `from` is zero, the tokens will be minted for `to`.
           * - When `to` is zero, ``from``'s tokens will be burned.
           * - `from` and `to` are never both zero.
           * - `batchSize` is non-zero.
           *
           * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
           */
          function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
          /**
           * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
           * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
           *
           * Calling conditions:
           *
           * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
           * - When `from` is zero, the tokens were minted for `to`.
           * - When `to` is zero, ``from``'s tokens were burned.
           * - `from` and `to` are never both zero.
           * - `batchSize` is non-zero.
           *
           * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
           */
          function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
          /**
           * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
           *
           * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
           * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
           * that `ownerOf(tokenId)` is `a`.
           */
          // solhint-disable-next-line func-name-mixedcase
          function __unsafe_increaseBalance(address account, uint256 amount) internal {
              _balances[account] += amount;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
      pragma solidity ^0.8.0;
      import "../IERC721.sol";
      /**
       * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
       * @dev See https://eips.ethereum.org/EIPS/eip-721
       */
      interface IERC721Metadata is IERC721 {
          /**
           * @dev Returns the token collection name.
           */
          function name() external view returns (string memory);
          /**
           * @dev Returns the token collection symbol.
           */
          function symbol() external view returns (string memory);
          /**
           * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
           */
          function tokenURI(uint256 tokenId) external view returns (string memory);
      }
      // 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 (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
      pragma solidity ^0.8.0;
      /**
       * @title ERC721 token receiver interface
       * @dev Interface for any contract that wants to support safeTransfers
       * from ERC721 asset contracts.
       */
      interface IERC721Receiver {
          /**
           * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
           * by `operator` from `from`, this function is called.
           *
           * It must return its Solidity selector to confirm the token transfer.
           * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
           *
           * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
           */
          function onERC721Received(
              address operator,
              address from,
              uint256 tokenId,
              bytes calldata data
          ) external returns (bytes4);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
      pragma solidity ^0.8.1;
      /**
       * @dev Collection of functions related to the address type
       */
      library Address {
          /**
           * @dev Returns true if `account` is a contract.
           *
           * [IMPORTANT]
           * ====
           * It is unsafe to assume that an address for which this function returns
           * false is an externally-owned account (EOA) and not a contract.
           *
           * Among others, `isContract` will return false for the following
           * types of addresses:
           *
           *  - an externally-owned account
           *  - a contract in construction
           *  - an address where a contract will be created
           *  - an address where a contract lived, but was destroyed
           *
           * Furthermore, `isContract` will also return true if the target contract within
           * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
           * which only has an effect at the end of a transaction.
           * ====
           *
           * [IMPORTANT]
           * ====
           * You shouldn't rely on `isContract` to protect against flash loan attacks!
           *
           * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
           * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
           * constructor.
           * ====
           */
          function isContract(address account) internal view returns (bool) {
              // This method relies on extcodesize/address.code.length, which returns 0
              // for contracts in construction, since the code is only stored at the end
              // of the constructor execution.
              return account.code.length > 0;
          }
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
              (bool success, ) = recipient.call{value: amount}("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain `call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, "Address: low-level call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
              return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
          }
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value,
              string memory errorMessage
          ) internal returns (bytes memory) {
              require(address(this).balance >= value, "Address: insufficient balance for call");
              (bool success, bytes memory returndata) = target.call{value: value}(data);
              return verifyCallResultFromTarget(target, success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
              return functionStaticCall(target, data, "Address: low-level static call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal view returns (bytes memory) {
              (bool success, bytes memory returndata) = target.staticcall(data);
              return verifyCallResultFromTarget(target, success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionDelegateCall(target, data, "Address: low-level delegate call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              (bool success, bytes memory returndata) = target.delegatecall(data);
              return verifyCallResultFromTarget(target, success, returndata, errorMessage);
          }
          /**
           * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
           * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
           *
           * _Available since v4.8._
           */
          function verifyCallResultFromTarget(
              address target,
              bool success,
              bytes memory returndata,
              string memory errorMessage
          ) internal view returns (bytes memory) {
              if (success) {
                  if (returndata.length == 0) {
                      // only check isContract if the call was successful and the return data is empty
                      // otherwise we already know that it was a contract
                      require(isContract(target), "Address: call to non-contract");
                  }
                  return returndata;
              } else {
                  _revert(returndata, errorMessage);
              }
          }
          /**
           * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
           * revert reason or using the provided one.
           *
           * _Available since v4.3._
           */
          function verifyCallResult(
              bool success,
              bytes memory returndata,
              string memory errorMessage
          ) internal pure returns (bytes memory) {
              if (success) {
                  return returndata;
              } else {
                  _revert(returndata, errorMessage);
              }
          }
          function _revert(bytes memory returndata, string memory errorMessage) private pure {
              // Look for revert reason and bubble it up if present
              if (returndata.length > 0) {
                  // The easiest way to bubble the revert reason is using memory via assembly
                  /// @solidity memory-safe-assembly
                  assembly {
                      let returndata_size := mload(returndata)
                      revert(add(32, returndata), returndata_size)
                  }
              } else {
                  revert(errorMessage);
              }
          }
      }
      // 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 v4.4.1 (utils/Counters.sol)
      pragma solidity ^0.8.0;
      /**
       * @title Counters
       * @author Matt Condon (@shrugs)
       * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
       * of elements in a mapping, issuing ERC721 ids, or counting request ids.
       *
       * Include with `using Counters for Counters.Counter;`
       */
      library Counters {
          struct Counter {
              // This variable should never be directly accessed by users of the library: interactions must be restricted to
              // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
              // this feature: see https://github.com/ethereum/solidity/issues/4637
              uint256 _value; // default: 0
          }
          function current(Counter storage counter) internal view returns (uint256) {
              return counter._value;
          }
          function increment(Counter storage counter) internal {
              unchecked {
                  counter._value += 1;
              }
          }
          function decrement(Counter storage counter) internal {
              uint256 value = counter._value;
              require(value > 0, "Counter: decrement overflow");
              unchecked {
                  counter._value = value - 1;
              }
          }
          function reset(Counter storage counter) internal {
              counter._value = 0;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
      pragma solidity ^0.8.0;
      import "./IERC165.sol";
      /**
       * @dev Implementation of the {IERC165} interface.
       *
       * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
       * for the additional interface id that will be supported. For example:
       *
       * ```solidity
       * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
       *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
       * }
       * ```
       *
       * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
       */
      abstract contract ERC165 is IERC165 {
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(IERC165).interfaceId;
          }
      }
      // 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
      // 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
      pragma solidity ^0.8.0;
      import "./interfaces/IGateway.sol";
      import "./interfaces/IBasicERC721.sol";
      import "./management/GatewayGuardedOwnable.sol";
      import "@limitbreak/creator-token-contracts/contracts/erc721c/ERC721C.sol";
      import "@limitbreak/creator-token-contracts/contracts/access/OwnableBasic.sol";
      import "@openzeppelin/contracts/security/Pausable.sol";
      import "@openzeppelin/contracts/utils/Strings.sol";
      import "@openzeppelin/contracts/utils/Counters.sol";
      import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
      /**
       * @title BasicERC721C
       * @author Libeccio Inc.
       * @notice Extension of ERC721C that adds access control through TokenGateway.
       */
      contract BasicERC721C is
          IBasicERC721,
          ERC2771Context,
          ERC721C,
          OwnableBasic,
          GatewayGuardedOwnable,
          Pausable
      {
          using Counters for Counters.Counter;
          uint256 public constant VERSION_BasicERC721C = 20240129;
          Counters.Counter private _tokenIdCounter;
          string private __baseURI;
          uint256 public maxTokenId;
          /**
           * @param name the NFT contract name
           * @param symbol the NFT contract symbol
           * @param baseURI the base uri for nft meta. Note that the meta uri for the speicied token will be "{baseURI}/{contractAddress}/{tokenId}"
           * @param gateway the NFTGateway contract address
           * @param trustedForwarder the trusted forwarder contract address used for ERC2771
           */
          constructor(
              string memory name,
              string memory symbol,
              string memory baseURI,
              address gateway,
              address trustedForwarder,
              uint256 _maxTokenId
          )
              ERC2771Context(trustedForwarder)
              ERC721OpenZeppelin(name, symbol)
              GatewayGuarded(gateway)
          {
              __baseURI = baseURI;
              _tokenIdCounter.increment();
              maxTokenId = _maxTokenId;
          }
          function incTokenIdCounter(uint256 limit) public returns (uint256) {
              uint256 id = _tokenIdCounter.current();
              limit = id + limit; // to avoid out of gas
              while (id < limit) {
                  if (!_exists(id)) {
                      return id;
                  }
                  _tokenIdCounter.increment();
                  id = _tokenIdCounter.current();
              }
              return id;
          }
          /**
           * Mint `tokenId` to `to`. If `tokenId` is 0, use auto-increment id.
           */
          function mint(
              address to,
              uint256 tokenId
          ) external override onlyGatewayOrOwner {
              if (tokenId == 0) {
                  tokenId = incTokenIdCounter(4096);
              }
              _safeMint(to, tokenId);
          }
          /**
           * Batch mint `tokenId` to `to`.
           */
          function mintBatch(
              address to,
              uint256[] calldata tokenId
          ) external override onlyGatewayOrOwner {
              for (uint256 i = 0; i < tokenId.length; i++) {
                  _safeMint(to, tokenId[i]);
              }
          }
          function _safeMint(address to, uint256 tokenId) internal virtual override {
              require(
                  maxTokenId == 0 || tokenId <= maxTokenId,
                  "ERC721: invalid, tokenId > maxTokenId"
              );
              _safeMint(to, tokenId, "");
          }
          /**
           * @dev Burns `tokenId`. See {ERC721-_burn}.
           *
           * Requirements:
           *
           * - The caller must own `tokenId` or be an approved operator.
           */
          function burn(uint256 tokenId) public virtual {
              require(
                  _isApprovedOrOwner(_msgSender(), tokenId),
                  "ERC721: caller is not token owner or approved"
              );
              _burn(tokenId);
          }
          function tokenURI(
              uint256 tokenId
          ) public view override returns (string memory) {
              return
                  string(
                      abi.encodePacked(
                          __baseURI,
                          "/",
                          Strings.toHexString(uint160(address(this)), 20),
                          "/",
                          Strings.toHexString(tokenId, 32)
                      )
                  );
          }
          function contractURI() public view returns (string memory) {
              return
                  string(
                      abi.encodePacked(
                          __baseURI,
                          "/",
                          Strings.toHexString(uint160(address(this)), 20)
                      )
                  );
          }
          function setMaxTokenID(uint256 _maxTokenId) external onlyGatewayOrOwner {
              maxTokenId = _maxTokenId;
          }
          function setURI(
              string calldata newBaseURI
          ) external override onlyGatewayOrOwner {
              __baseURI = newBaseURI;
          }
          function pause() external onlyGatewayOrOwner {
              _pause();
          }
          function unpause() external onlyGatewayOrOwner {
              _unpause();
          }
          function supportsInterface(
              bytes4 interfaceId
          ) public view virtual override returns (bool) {
              return
                  interfaceId == type(IBasicERC721).interfaceId ||
                  super.supportsInterface(interfaceId);
          }
          function _beforeTokenTransfer(
              address from,
              address to,
              uint256 firstTokenId,
              uint256 batchSize
          ) internal virtual override {
              _requireNotPaused();
              super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
          }
          function _msgSender()
              internal
              view
              virtual
              override(ERC2771Context, Context)
              returns (address sender)
          {
              return ERC2771Context._msgSender();
          }
          function _msgData()
              internal
              view
              virtual
              override(ERC2771Context, Context)
              returns (bytes calldata)
          {
              return ERC2771Context._msgData();
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./BasicERC721C.sol";
      import "@limitbreak/creator-token-contracts/contracts/programmable-royalties/BasicRoyalties.sol";
      /**
       * @title BasicERC721CWithBasicRoyalties
       * @author Libeccio Inc.
       * @notice Extension of BasicERC721C that adds basic royalties support.
       */
      contract BasicERC721CWithBasicRoyalties is BasicERC721C, BasicRoyalties {
          uint256 public constant VERSION_BasicERC721CWithBasicRoyalties = 20240129;
          constructor(
              string memory name,
              string memory symbol,
              string memory baseURI,
              address gateway,
              address trustedForwarder,
              address royaltyReceiver,
              uint96 royaltyFeeNumerator,
              uint256 _maxTokenId
          )
              BasicERC721C(
                  name,
                  symbol,
                  baseURI,
                  gateway,
                  trustedForwarder,
                  _maxTokenId
              )
              BasicRoyalties(royaltyReceiver, royaltyFeeNumerator)
          {}
          function supportsInterface(
              bytes4 interfaceId
          ) public view virtual override(BasicERC721C, ERC2981) returns (bool) {
              return super.supportsInterface(interfaceId);
          }
          function setDefaultRoyalty(
              address receiver,
              uint96 feeNumerator
          ) external onlyGatewayOrOwner {
              super._setDefaultRoyalty(receiver, feeNumerator);
          }
          function setTokenRoyalty(
              uint256 tokenId,
              address receiver,
              uint96 feeNumerator
          ) external onlyGatewayOrOwner {
              super._setTokenRoyalty(tokenId, receiver, feeNumerator);
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IBasicERC721 {
          function mint(address to, uint256 tokenId) external;
          function mintBatch(address to, uint256[] calldata tokenId) external;
          function setURI(string calldata newBaseURI) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IERC1155Gateway {
          /********************************************************************
           *                        ERC1155 interfaces                        *
           ********************************************************************/
          /**
           * @dev Mint ERC1155 tokens.
           * @param account receiver of the minted tokens
           * @param id id of tokens to be minted
           * @param amount amount of tokens to be minted
           */
          function ERC1155_mint(
              address nftContract,
              address account,
              uint256 id,
              uint256 amount,
              bytes memory data
          ) external;
          /**
           * @dev Mint a batch of ERC1155 tokens.
           *
           * See {ERC1155_mint}
           */
          function ERC1155_mintBatch(
              address nftContract,
              address to,
              uint256[] memory ids,
              uint256[] memory amounts,
              bytes memory data
          ) external;
          /**
           * @dev Sets a new URI for all token types, by relying on the token type ID
           * substitution mechanism
           * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
           *
           * By this mechanism, any occurrence of the `\\{id\\}` substring in either the
           * URI or any of the amounts in the JSON file at said URI will be replaced by
           * clients with the token type ID.
           *
           * For example, the `https://token-cdn-domain/\\{id\\}.json` URI would be
           * interpreted by clients as
           * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
           * for token type ID 0x4cce0.
           *
           * See {uri}.
           *
           * Because these URIs cannot be meaningfully represented by the {URI} event,
           * this function emits no events.
           */
          function ERC1155_setURI(address nftContract, string memory newuri) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IERC20Gateway {
          /********************************************************************
           *                         ERC20 interfaces                         *
           ********************************************************************/
          /**
           * @dev Mint some ERC20 tokens to the recipient address.
           * @notice Only gateway contract is authorized to mint.
           * @param recipient The recipient of the minted ERC20 tokens.
           * @param amount The amount to be minted.
           */
          function ERC20_mint(
              address erc20Contract,
              address recipient,
              uint256 amount
          ) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      interface IERC721Gateway {
          /********************************************************************
           *                        ERC721 interfaces                         *
           ********************************************************************/
          /**
           * @dev Mint an ERC721 token to the given address.
           * @notice Only gateway contract is authorized to mint.
           * @param recipient The recipient of the minted NFT.
           * @param tokenId The tokenId to be minted.
           */
          function ERC721_mint(
              address nftContract,
              address recipient,
              uint256 tokenId
          ) external;
          function ERC721_mintBatch(
              address nftContract,
              address recipient,
              uint256[] calldata tokenId
          ) external;
          /**
           * @dev Set `baseURI` of the ERC721 token. If set, the resulting URI for each
           * token will be the concatenation of the `baseURI` and the `tokenId`.
           */
          function ERC721_setURI(address nftContract, string memory newURI) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./IERC721Gateway.sol";
      import "./IERC1155Gateway.sol";
      import "./IERC20Gateway.sol";
      interface IGateway is IERC721Gateway, IERC1155Gateway, IERC20Gateway {
          function operatorWhitelist(address _operator) external view returns (bool);
          function setManagerOf(address _nftContract, address _manager) external;
          function nftManager(address _nftContract) external view returns (address);
          function isInManagement(
              address _x,
              address _tokenContract
          ) external view returns (bool);
          function pause(address _contract) external;
          function unpause(address _contract) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      /**
       * The management interface exposed to gateway.
       */
      interface IGatewayGuarded {
          /**
           * @dev Set the gateway contract address.
           * @notice Only gateway contract is authorized to set a
           * new gateway address.
           * @notice This function should be rarely used.
           * @param gateway The new gateway address.
           */
          function setGateway(address gateway) external;
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "../interfaces/IGatewayGuarded.sol";
      /**
       * The management interface exposed to gateway.
       */
      abstract contract GatewayGuarded is IGatewayGuarded {
          
          address public gateway;
          modifier onlyGateway() {
              _checkGateway();
              _;
          }
          constructor(address _gateway) {
              gateway = _gateway;
          }
          /**
           * @dev Throws if the sender is not the gateway contract.
           */
          function _checkGateway() internal view virtual {
              require(gateway == msg.sender, "GatewayGuarded: caller is not the gateway");
          }
          /**
           * @inheritdoc IGatewayGuarded
           */
          function setGateway(address _gateway) external override onlyGateway {
              gateway = _gateway;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./GatewayGuarded.sol";
      import "@openzeppelin/contracts/access/Ownable.sol";
      /**
       * The management interface exposed to gateway.
       */
      abstract contract GatewayGuardedOwnable is GatewayGuarded, Ownable {
          
          modifier onlyGatewayOrOwner() {
              _checkGatewayOrOwner();
              _;
          }
          /**
           * @dev Throws if the sender is neither the gateway contract nor the owner.
           */
          function _checkGatewayOrOwner() internal view virtual {
              address sender = _msgSender();
              require(gateway == sender || owner() == sender, "GatewayGuardedOwnable: caller is neither the gateway nor the owner");
          }
          function resetOwner(address _newOwner) external onlyGateway {
              _transferOwnership(_newOwner);
          }
      }
      

      File 2 of 4: TransparentUpgradeableProxy
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
      import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
      import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
      import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
      import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
      // Kept for backwards compatibility with older versions of Hardhat and Truffle plugins.
      contract AdminUpgradeabilityProxy is TransparentUpgradeableProxy {
          constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) {}
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./IBeacon.sol";
      import "../Proxy.sol";
      import "../ERC1967/ERC1967Upgrade.sol";
      /**
       * @dev This contract implements a proxy that gets the implementation address for each call from a {UpgradeableBeacon}.
       *
       * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't
       * conflict with the storage layout of the implementation behind the proxy.
       *
       * _Available since v3.4._
       */
      contract BeaconProxy is Proxy, ERC1967Upgrade {
          /**
           * @dev Initializes the proxy with `beacon`.
           *
           * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
           * will typically be an encoded function call, and allows initializating the storage of the proxy like a Solidity
           * constructor.
           *
           * Requirements:
           *
           * - `beacon` must be a contract with the interface {IBeacon}.
           */
          constructor(address beacon, bytes memory data) payable {
              assert(_BEACON_SLOT == bytes32(uint256(keccak256("eip1967.proxy.beacon")) - 1));
              _upgradeBeaconToAndCall(beacon, data, false);
          }
          /**
           * @dev Returns the current beacon address.
           */
          function _beacon() internal view virtual returns (address) {
              return _getBeacon();
          }
          /**
           * @dev Returns the current implementation address of the associated beacon.
           */
          function _implementation() internal view virtual override returns (address) {
              return IBeacon(_getBeacon()).implementation();
          }
          /**
           * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}.
           *
           * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon.
           *
           * Requirements:
           *
           * - `beacon` must be a contract.
           * - The implementation returned by `beacon` must be a contract.
           */
          function _setBeacon(address beacon, bytes memory data) internal virtual {
              _upgradeBeaconToAndCall(beacon, data, false);
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./IBeacon.sol";
      import "../../access/Ownable.sol";
      import "../../utils/Address.sol";
      /**
       * @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
       * implementation contract, which is where they will delegate all function calls.
       *
       * An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
       */
      contract UpgradeableBeacon is IBeacon, Ownable {
          address private _implementation;
          /**
           * @dev Emitted when the implementation returned by the beacon is changed.
           */
          event Upgraded(address indexed implementation);
          /**
           * @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the
           * beacon.
           */
          constructor(address implementation_) {
              _setImplementation(implementation_);
          }
          /**
           * @dev Returns the current implementation address.
           */
          function implementation() public view virtual override returns (address) {
              return _implementation;
          }
          /**
           * @dev Upgrades the beacon to a new implementation.
           *
           * Emits an {Upgraded} event.
           *
           * Requirements:
           *
           * - msg.sender must be the owner of the contract.
           * - `newImplementation` must be a contract.
           */
          function upgradeTo(address newImplementation) public virtual onlyOwner {
              _setImplementation(newImplementation);
              emit Upgraded(newImplementation);
          }
          /**
           * @dev Sets the implementation contract address for this beacon
           *
           * Requirements:
           *
           * - `newImplementation` must be a contract.
           */
          function _setImplementation(address newImplementation) private {
              require(Address.isContract(newImplementation), "UpgradeableBeacon: implementation is not a contract");
              _implementation = newImplementation;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "../Proxy.sol";
      import "./ERC1967Upgrade.sol";
      /**
       * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
       * implementation address that can be changed. This address is stored in storage in the location specified by
       * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
       * implementation behind the proxy.
       */
      contract ERC1967Proxy is Proxy, ERC1967Upgrade {
          /**
           * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
           *
           * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
           * function call, and allows initializating the storage of the proxy like a Solidity constructor.
           */
          constructor(address _logic, bytes memory _data) payable {
              assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
              _upgradeToAndCall(_logic, _data, false);
          }
          /**
           * @dev Returns the current implementation address.
           */
          function _implementation() internal view virtual override returns (address impl) {
              return ERC1967Upgrade._getImplementation();
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "../ERC1967/ERC1967Proxy.sol";
      /**
       * @dev This contract implements a proxy that is upgradeable by an admin.
       *
       * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
       * clashing], which can potentially be used in an attack, this contract uses the
       * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
       * things that go hand in hand:
       *
       * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
       * that call matches one of the admin functions exposed by the proxy itself.
       * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
       * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
       * "admin cannot fallback to proxy target".
       *
       * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
       * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
       * to sudden errors when trying to call a function from the proxy implementation.
       *
       * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
       * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
       */
      contract TransparentUpgradeableProxy is ERC1967Proxy {
          /**
           * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
           * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
           */
          constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
              assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
              _changeAdmin(admin_);
          }
          /**
           * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
           */
          modifier ifAdmin() {
              if (msg.sender == _getAdmin()) {
                  _;
              } else {
                  _fallback();
              }
          }
          /**
           * @dev Returns the current admin.
           *
           * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
           *
           * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
           * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
           * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
           */
          function admin() external ifAdmin returns (address admin_) {
              admin_ = _getAdmin();
          }
          /**
           * @dev Returns the current implementation.
           *
           * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
           *
           * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
           * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
           * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
           */
          function implementation() external ifAdmin returns (address implementation_) {
              implementation_ = _implementation();
          }
          /**
           * @dev Changes the admin of the proxy.
           *
           * Emits an {AdminChanged} event.
           *
           * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
           */
          function changeAdmin(address newAdmin) external virtual ifAdmin {
              _changeAdmin(newAdmin);
          }
          /**
           * @dev Upgrade the implementation of the proxy.
           *
           * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
           */
          function upgradeTo(address newImplementation) external ifAdmin {
              _upgradeToAndCall(newImplementation, bytes(""), false);
          }
          /**
           * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
           * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
           * proxied contract.
           *
           * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
           */
          function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
              _upgradeToAndCall(newImplementation, data, true);
          }
          /**
           * @dev Returns the current admin.
           */
          function _admin() internal view virtual returns (address) {
              return _getAdmin();
          }
          /**
           * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
           */
          function _beforeFallback() internal virtual override {
              require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
              super._beforeFallback();
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "./TransparentUpgradeableProxy.sol";
      import "../../access/Ownable.sol";
      /**
       * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
       * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
       */
      contract ProxyAdmin is Ownable {
          /**
           * @dev Returns the current implementation of `proxy`.
           *
           * Requirements:
           *
           * - This contract must be the admin of `proxy`.
           */
          function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
              // We need to manually run the static call since the getter cannot be flagged as view
              // bytes4(keccak256("implementation()")) == 0x5c60da1b
              (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
              require(success);
              return abi.decode(returndata, (address));
          }
          /**
           * @dev Returns the current admin of `proxy`.
           *
           * Requirements:
           *
           * - This contract must be the admin of `proxy`.
           */
          function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
              // We need to manually run the static call since the getter cannot be flagged as view
              // bytes4(keccak256("admin()")) == 0xf851a440
              (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
              require(success);
              return abi.decode(returndata, (address));
          }
          /**
           * @dev Changes the admin of `proxy` to `newAdmin`.
           *
           * Requirements:
           *
           * - This contract must be the current admin of `proxy`.
           */
          function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
              proxy.changeAdmin(newAdmin);
          }
          /**
           * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
           *
           * Requirements:
           *
           * - This contract must be the admin of `proxy`.
           */
          function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
              proxy.upgradeTo(implementation);
          }
          /**
           * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
           * {TransparentUpgradeableProxy-upgradeToAndCall}.
           *
           * Requirements:
           *
           * - This contract must be the admin of `proxy`.
           */
          function upgradeAndCall(TransparentUpgradeableProxy proxy, address implementation, bytes memory data) public payable virtual onlyOwner {
              proxy.upgradeToAndCall{value: msg.value}(implementation, data);
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      /**
       * @dev This is the interface that {BeaconProxy} expects of its beacon.
       */
      interface IBeacon {
          /**
           * @dev Must return an address that can be used as a delegate call target.
           *
           * {BeaconProxy} will check that this address is a contract.
           */
          function implementation() external view returns (address);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      /**
       * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
       * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
       * be specified by overriding the virtual {_implementation} function.
       *
       * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
       * different contract through the {_delegate} function.
       *
       * The success and return data of the delegated call will be returned back to the caller of the proxy.
       */
      abstract contract Proxy {
          /**
           * @dev Delegates the current call to `implementation`.
           *
           * This function does not return to its internall call site, it will return directly to the external caller.
           */
          function _delegate(address implementation) internal virtual {
              // solhint-disable-next-line no-inline-assembly
              assembly {
                  // Copy msg.data. We take full control of memory in this inline assembly
                  // block because it will not return to Solidity code. We overwrite the
                  // Solidity scratch pad at memory position 0.
                  calldatacopy(0, 0, calldatasize())
                  // Call the implementation.
                  // out and outsize are 0 because we don't know the size yet.
                  let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                  // Copy the returned data.
                  returndatacopy(0, 0, returndatasize())
                  switch result
                  // delegatecall returns 0 on error.
                  case 0 { revert(0, returndatasize()) }
                  default { return(0, returndatasize()) }
              }
          }
          /**
           * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
           * and {_fallback} should delegate.
           */
          function _implementation() internal view virtual returns (address);
          /**
           * @dev Delegates the current call to the address returned by `_implementation()`.
           *
           * This function does not return to its internall call site, it will return directly to the external caller.
           */
          function _fallback() internal virtual {
              _beforeFallback();
              _delegate(_implementation());
          }
          /**
           * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
           * function in the contract matches the call data.
           */
          fallback () external payable virtual {
              _fallback();
          }
          /**
           * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
           * is empty.
           */
          receive () external payable virtual {
              _fallback();
          }
          /**
           * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
           * call, or as part of the Solidity `fallback` or `receive` functions.
           *
           * If overriden should call `super._beforeFallback()`.
           */
          function _beforeFallback() internal virtual {
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.2;
      import "../beacon/IBeacon.sol";
      import "../../utils/Address.sol";
      import "../../utils/StorageSlot.sol";
      /**
       * @dev This abstract contract provides getters and event emitting update functions for
       * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
       *
       * _Available since v4.1._
       *
       * @custom:oz-upgrades-unsafe-allow delegatecall
       */
      abstract contract ERC1967Upgrade {
          // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
          bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
          /**
           * @dev Storage slot with the address of the current implementation.
           * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
           * validated in the constructor.
           */
          bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
          /**
           * @dev Emitted when the implementation is upgraded.
           */
          event Upgraded(address indexed implementation);
          /**
           * @dev Returns the current implementation address.
           */
          function _getImplementation() internal view returns (address) {
              return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
          }
          /**
           * @dev Stores a new address in the EIP1967 implementation slot.
           */
          function _setImplementation(address newImplementation) private {
              require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
              StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
          }
          /**
           * @dev Perform implementation upgrade
           *
           * Emits an {Upgraded} event.
           */
          function _upgradeTo(address newImplementation) internal {
              _setImplementation(newImplementation);
              emit Upgraded(newImplementation);
          }
          /**
           * @dev Perform implementation upgrade with additional setup call.
           *
           * Emits an {Upgraded} event.
           */
          function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
              _setImplementation(newImplementation);
              emit Upgraded(newImplementation);
              if (data.length > 0 || forceCall) {
                  Address.functionDelegateCall(newImplementation, data);
              }
          }
          /**
           * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
           *
           * Emits an {Upgraded} event.
           */
          function _upgradeToAndCallSecure(address newImplementation, bytes memory data, bool forceCall) internal {
              address oldImplementation = _getImplementation();
              // Initial upgrade and setup call
              _setImplementation(newImplementation);
              if (data.length > 0 || forceCall) {
                  Address.functionDelegateCall(newImplementation, data);
              }
              // Perform rollback test if not already in progress
              StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT);
              if (!rollbackTesting.value) {
                  // Trigger rollback using upgradeTo from the new implementation
                  rollbackTesting.value = true;
                  Address.functionDelegateCall(
                      newImplementation,
                      abi.encodeWithSignature(
                          "upgradeTo(address)",
                          oldImplementation
                      )
                  );
                  rollbackTesting.value = false;
                  // Check rollback was effective
                  require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
                  // Finally reset to the new implementation and log the upgrade
                  _setImplementation(newImplementation);
                  emit Upgraded(newImplementation);
              }
          }
          /**
           * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
           * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
           *
           * Emits a {BeaconUpgraded} event.
           */
          function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
              _setBeacon(newBeacon);
              emit BeaconUpgraded(newBeacon);
              if (data.length > 0 || forceCall) {
                  Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
              }
          }
          /**
           * @dev Storage slot with the admin of the contract.
           * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
           * validated in the constructor.
           */
          bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
          /**
           * @dev Emitted when the admin account has changed.
           */
          event AdminChanged(address previousAdmin, address newAdmin);
          /**
           * @dev Returns the current admin.
           */
          function _getAdmin() internal view returns (address) {
              return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
          }
          /**
           * @dev Stores a new address in the EIP1967 admin slot.
           */
          function _setAdmin(address newAdmin) private {
              require(newAdmin != address(0), "ERC1967: new admin is the zero address");
              StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
          }
          /**
           * @dev Changes the admin of the proxy.
           *
           * Emits an {AdminChanged} event.
           */
          function _changeAdmin(address newAdmin) internal {
              emit AdminChanged(_getAdmin(), newAdmin);
              _setAdmin(newAdmin);
          }
          /**
           * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
           * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
           */
          bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
          /**
           * @dev Emitted when the beacon is upgraded.
           */
          event BeaconUpgraded(address indexed beacon);
          /**
           * @dev Returns the current beacon.
           */
          function _getBeacon() internal view returns (address) {
              return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
          }
          /**
           * @dev Stores a new beacon in the EIP1967 beacon slot.
           */
          function _setBeacon(address newBeacon) private {
              require(
                  Address.isContract(newBeacon),
                  "ERC1967: new beacon is not a contract"
              );
              require(
                  Address.isContract(IBeacon(newBeacon).implementation()),
                  "ERC1967: beacon implementation is not a contract"
              );
              StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      /**
       * @dev Collection of functions related to the address type
       */
      library Address {
          /**
           * @dev Returns true if `account` is a contract.
           *
           * [IMPORTANT]
           * ====
           * It is unsafe to assume that an address for which this function returns
           * false is an externally-owned account (EOA) and not a contract.
           *
           * Among others, `isContract` will return false for the following
           * types of addresses:
           *
           *  - an externally-owned account
           *  - a contract in construction
           *  - an address where a contract will be created
           *  - an address where a contract lived, but was destroyed
           * ====
           */
          function isContract(address account) internal view returns (bool) {
              // This method relies on extcodesize, which returns 0 for contracts in
              // construction, since the code is only stored at the end of the
              // constructor execution.
              uint256 size;
              // solhint-disable-next-line no-inline-assembly
              assembly { size := extcodesize(account) }
              return size > 0;
          }
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
              // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
              (bool success, ) = recipient.call{ value: amount }("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain`call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCall(target, data, "Address: low-level call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
              return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
          }
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
              require(address(this).balance >= value, "Address: insufficient balance for call");
              require(isContract(target), "Address: call to non-contract");
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.call{ value: value }(data);
              return _verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
              return functionStaticCall(target, data, "Address: low-level static call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
              require(isContract(target), "Address: static call to non-contract");
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.staticcall(data);
              return _verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionDelegateCall(target, data, "Address: low-level delegate call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
              require(isContract(target), "Address: delegate call to non-contract");
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.delegatecall(data);
              return _verifyCallResult(success, returndata, errorMessage);
          }
          function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
              if (success) {
                  return returndata;
              } else {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      /**
       * @dev Library for reading and writing primitive types to specific storage slots.
       *
       * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
       * This library helps with reading and writing to such slots without the need for inline assembly.
       *
       * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
       *
       * Example usage to set ERC1967 implementation slot:
       * ```
       * contract ERC1967 {
       *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
       *
       *     function _getImplementation() internal view returns (address) {
       *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
       *     }
       *
       *     function _setImplementation(address newImplementation) internal {
       *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
       *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
       *     }
       * }
       * ```
       *
       * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
       */
      library StorageSlot {
          struct AddressSlot {
              address value;
          }
          struct BooleanSlot {
              bool value;
          }
          struct Bytes32Slot {
              bytes32 value;
          }
          struct Uint256Slot {
              uint256 value;
          }
          /**
           * @dev Returns an `AddressSlot` with member `value` located at `slot`.
           */
          function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
              assembly {
                  r.slot := slot
              }
          }
          /**
           * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
           */
          function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
              assembly {
                  r.slot := slot
              }
          }
          /**
           * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
           */
          function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
              assembly {
                  r.slot := slot
              }
          }
          /**
           * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
           */
          function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
              assembly {
                  r.slot := slot
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      import "../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 {
          address private _owner;
          event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
          /**
           * @dev Initializes the contract setting the deployer as the initial owner.
           */
          constructor () {
              address msgSender = _msgSender();
              _owner = msgSender;
              emit OwnershipTransferred(address(0), msgSender);
          }
          /**
           * @dev Returns the address of the current owner.
           */
          function owner() public view virtual returns (address) {
              return _owner;
          }
          /**
           * @dev Throws if called by any account other than the owner.
           */
          modifier onlyOwner() {
              require(owner() == _msgSender(), "Ownable: caller is not the owner");
              _;
          }
          /**
           * @dev Leaves the contract without owner. It will not be possible to call
           * `onlyOwner` functions anymore. Can only be called by the current owner.
           *
           * NOTE: Renouncing ownership will leave the contract without an owner,
           * thereby removing any functionality that is only available to the owner.
           */
          function renounceOwnership() public virtual onlyOwner {
              emit OwnershipTransferred(_owner, address(0));
              _owner = 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 {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              emit OwnershipTransferred(_owner, newOwner);
              _owner = newOwner;
          }
      }
      // SPDX-License-Identifier: MIT
      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) {
              this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
              return msg.data;
          }
      }
      

      File 3 of 4: CreatorTokenTransferValidator
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.24;
      import "../Constants.sol";
      import "../interfaces/IEOARegistry.sol";
      import "../interfaces/ITransferValidator.sol";
      import "./TransferPolicy.sol";
      import {CreatorTokenTransferValidatorConfiguration} from "./CreatorTokenTransferValidatorConfiguration.sol";
      import "@limitbreak/permit-c/PermitC.sol";
      import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
      import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
      import "@opensea/tstorish/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 configure 
       *         the transfer security level, blacklisted accounts and codehashes, whitelisted accounts and codehashes, and
       *         authorized accounts and codehashes for each collection.
       *
       * @dev    <h4>Features</h4>
       *         - Transfer security levels: Provides different levels of transfer security, 
       *           from open transfers to completely restricted transfers.
       *         - Blacklist: Allows the owner of a collection to blacklist specific operator addresses or codehashes
       *           from executing transfers on behalf of others.
       *         - Whitelist: Allows the owner of a collection to whitelist specific operator addresses or codehashes
       *           permitted to execute transfers on behalf of others or send/receive tokens when otherwise disabled by 
       *           security policy.
       *         - Authorizers: Allows the owner of a collection to enable authorizer contracts, that can perform 
       *           authorization-based filtering of transfers.
       *
       * @dev    <h4>Benefits</h4>
       *         - Enhanced security: Allows creators to have more control over their NFT collections, ensuring the safety 
       *           and integrity of their assets.
       *         - Flexibility: Provides collection owners the ability to customize transfer rules as per their requirements.
       *         - Compliance: Facilitates compliance with regulations by enabling creators to restrict transfers based on 
       *           specific criteria.
       *
       * @dev    <h4>Intended Usage</h4>
       *         - The CreatorTokenTransferValidatorV3 contract is intended to be used by NFT collection owners to manage and 
       *           enforce transfer policies. This contract is integrated with the following varations of creator token 
       *           NFT contracts to validate transfers according to the defined security policies.
       *
       *           - ERC721-C:   Creator token implenting OpenZeppelin's ERC-721 standard.
       *           - ERC721-AC:  Creator token implenting Azuki's ERC-721A standard.
       *           - ERC721-CW:  Creator token implementing OpenZeppelin's ERC-721 standard with opt-in staking to 
       *                         wrap/upgrade a pre-existing ERC-721 collection.
       *           - ERC721-ACW: Creator token implementing Azuki's ERC721-A standard with opt-in staking to 
       *                         wrap/upgrade a pre-existing ERC-721 collection.
       *           - ERC1155-C:  Creator token implenting OpenZeppelin's ERC-1155 standard.
       *           - ERC1155-CW: Creator token implementing OpenZeppelin's ERC-1155 standard with opt-in staking to 
       *                         wrap/upgrade a pre-existing ERC-1155 collection.
       *
       *          <h4>Transfer Security Levels</h4>
       *          - Recommended: Recommended defaults are same as Level 3 (Whitelisting with OTC Enabled).
       *            - Caller Constraints: OperatorWhitelistEnableOTC
       *            - Receiver Constraints: None
       *          - Level 1: No transfer restrictions.
       *            - Caller Constraints: None
       *            - Receiver Constraints: None
       *          - Level 2: Only non-blacklisted operators can initiate transfers, over-the-counter (OTC) trading enabled.
       *            - Caller Constraints: OperatorBlacklistEnableOTC
       *            - Receiver Constraints: None
       *          - Level 3: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading enabled.
       *            - Caller Constraints: OperatorWhitelistEnableOTC
       *            - Receiver Constraints: None
       *          - Level 4: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading disabled.
       *            - Caller Constraints: OperatorWhitelistDisableOTC
       *            - Receiver Constraints: None
       *          - Level 5: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading enabled. 
       *                     Transfers to contracts with code are not allowed, unless present on the whitelist.
       *            - Caller Constraints: OperatorWhitelistEnableOTC
       *            - Receiver Constraints: NoCode
       *          - Level 6: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading enabled. 
       *                     Transfers are allowed only to Externally Owned Accounts (EOAs), unless present on the whitelist.
       *            - Caller Constraints: OperatorWhitelistEnableOTC
       *            - Receiver Constraints: EOA
       *          - Level 7: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading disabled. 
       *                     Transfers to contracts with code are not allowed, unless present on the whitelist.
       *            - Caller Constraints: OperatorWhitelistDisableOTC
       *            - Receiver Constraints: NoCode
       *          - Level 8: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading disabled. 
       *                     Transfers are allowed only to Externally Owned Accounts (EOAs), unless present on the whitelist.
       *            - Caller Constraints: OperatorWhitelistDisableOTC
       *            - Receiver Constraints: EOA
       */
      contract CreatorTokenTransferValidator is IEOARegistry, ITransferValidator, ERC165, Tstorish, PermitC {
          using EnumerableSet for EnumerableSet.AddressSet;
          using EnumerableSet for EnumerableSet.Bytes32Set;
          /*************************************************************************/
          /*                             CUSTOM ERRORS                             */
          /*************************************************************************/
          /// @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 a function that requires the caller to be the list owner.
          error CreatorTokenTransferValidator__CallerDoesNotOwnList();
          /// @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 authorizing a transfer for a collection using authorizers and the msg.sender is not in the authorizer list.
          error CreatorTokenTransferValidator__CallerMustBeAnAuthorizer();
          /// @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 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 validating a transfer for a collection using blacklists and the operator is on the blacklist.
          error CreatorTokenTransferValidator__OperatorIsBlacklisted();
          /// @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 a frozen account is the receiver of a transfer
          error CreatorTokenTransferValidator__ReceiverAccountIsFrozen();
          /// @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 an authorizer attempts to set a wildcard authorized operator on collections that don't allow wildcards
          error CreatorTokenTransferValidator__WildcardOperatorsCannotBeAuthorizedForCollection();
          /// @dev Thrown when attempting to set a authorized operator when authorization mode is disabled.
          error CreatorTokenTransferValidator__AuthorizationDisabledForCollection();
          /// @dev Thrown when attempting to validate a permitted transfer where the permit type does not match the collection-defined token type.
          error CreatorTokenTransferValidator__TokenTypesDoNotMatch();
          /*************************************************************************/
          /*                                EVENTS                                 */
          /*************************************************************************/
          /// @dev Emitted when a new list is created.
          event CreatedList(uint256 indexed id, string name);
          /// @dev Emitted when a list is applied to a collection.
          event AppliedListToCollection(address indexed collection, uint120 indexed id);
          /// @dev Emitted when the ownership of a list is transferred to a new owner.
          event ReassignedListOwnership(uint256 indexed id, address indexed newOwner);
          /// @dev Emitted when an account is added to the list of frozen accounts for a collection.
          event AccountFrozenForCollection(address indexed collection, address indexed account);
          /// @dev Emitted when an account is removed from the list of frozen accounts for a collection.
          event AccountUnfrozenForCollection(address indexed collection, address indexed account);
          /// @dev Emitted when an address is added to a list.
          event AddedAccountToList(uint8 indexed kind, uint256 indexed id, address indexed account);
          /// @dev Emitted when a codehash is added to a list.
          event AddedCodeHashToList(uint8 indexed kind, uint256 indexed id, bytes32 indexed codehash);
          /// @dev Emitted when an address is removed from a list.
          event RemovedAccountFromList(uint8 indexed kind, uint256 indexed id, address indexed account);
          /// @dev Emitted when a codehash is removed from a list.
          event RemovedCodeHashFromList(uint8 indexed kind, uint256 indexed id, bytes32 indexed codehash);
          /// @dev Emitted when the security level for a collection is updated.
          event SetTransferSecurityLevel(address indexed collection, uint8 level);
          /// @dev Emitted when a collection updates its authorization mode.
          event SetAuthorizationModeEnabled(address indexed collection, bool disabled, bool authorizersCannotSetWildcardOperators);
          /// @dev Emitted when a collection turns account freezing on or off.
          event SetAccountFreezingModeEnabled(address indexed collection, bool enabled);
          /// @dev Emitted when a collection's token type is updated.
          event SetTokenType(address indexed collection, uint16 tokenType);
          /*************************************************************************/
          /*                                STRUCTS                                */
          /*************************************************************************/
          /**
           * @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;
          }
          /*************************************************************************/
          /*                               CONSTANTS                               */
          /*************************************************************************/
          /// @dev Immutable lookup table for constant gas determination of caller constraints by security level.
          /// @dev Created during contract construction using defined constants.
          uint256 private immutable _callerConstraintsLookup;
          /// @dev Immutable lookup table for constant gas determination of receiver constraints by security level.
          /// @dev Created during contract construction using defined constants.
          uint256 private immutable _receiverConstraintsLookup;
          /// @dev The address of the EOA Registry to use to validate an account is a verified EOA.
          address private immutable _eoaRegistry;
          /// @dev The legacy Creator Token Transfer Validator Interface
          bytes4 private constant LEGACY_TRANSFER_VALIDATOR_INTERFACE_ID = 0x00000000;
          /// @dev The default admin role value for contracts that implement access control.
          bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;
          /// @dev Value representing a zero value code hash.
          bytes32 private constant BYTES32_ZERO = 0x0000000000000000000000000000000000000000000000000000000000000000;
          address private constant WILDCARD_OPERATOR_ADDRESS = address(0x01);
          uint16 private constant DEFAULT_TOKEN_TYPE = 0;
          /*************************************************************************/
          /*                                STORAGE                                */
          /*************************************************************************/
          /// @notice Keeps track of the most recently created list id.
          uint120 public lastListId;
          /// @notice Mapping of list ids to list owners
          mapping (uint120 => address) public listOwners;
          /// @dev Mapping of collection addresses to their security policy settings
          mapping (address => CollectionSecurityPolicyV3) internal collectionSecurityPolicies;
          /// @dev Mapping of list ids to blacklist settings
          mapping (uint120 => List) internal blacklists;
          /// @dev Mapping of list ids to whitelist settings
          mapping (uint120 => List) internal whitelists;
          /// @dev Mapping of list ids to authorizers
          mapping (uint120 => List) internal authorizers;
          /// @dev Mapping of collections to accounts that are frozen for those collections
          mapping (address => AccountList) internal frozenAccounts;
          constructor(
              address defaultOwner,
              address eoaRegistry_,
              string memory name,
              string memory version,
              address validatorConfiguration
          ) 
          Tstorish()
          PermitC(
              name,
              version,
              defaultOwner,
              CreatorTokenTransferValidatorConfiguration(validatorConfiguration).getNativeValueToCheckPauseState()
          ) {
              if (defaultOwner == address(0) || eoaRegistry_ == address(0)) {
                  revert CreatorTokenTransferValidator__InvalidConstructorArgs();
              }
              _createDefaultList(defaultOwner);
              _eoaRegistry = eoaRegistry_;
              _callerConstraintsLookup = _constructCallerConstraintsTable();
              _receiverConstraintsLookup = _constructReceiverConstraintsTable();
          }
          /**
           * @dev This function is only called during contract construction to create the default list.
           */
          function _createDefaultList(address defaultOwner) internal {
              uint120 id = 0;
              listOwners[id] = defaultOwner;
              emit CreatedList(id, "DEFAULT LIST");
              emit ReassignedListOwnership(id, defaultOwner);
          }
          /**
           * @dev This function is only called during contract construction to create the caller constraints
           * @dev lookup table.
           */
          function _constructCallerConstraintsTable() internal pure returns (uint256) {
              return 
              (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_RECOMMENDED << 3))
                  | (CALLER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_ONE << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_BLACKLIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_TWO << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_THREE << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC << (TRANSFER_SECURITY_LEVEL_FOUR << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_FIVE << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_SIX << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC << (TRANSFER_SECURITY_LEVEL_SEVEN << 3))
                  | (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC << (TRANSFER_SECURITY_LEVEL_EIGHT << 3))
                  | (CALLER_CONSTRAINTS_SBT << (TRANSFER_SECURITY_LEVEL_NINE << 3));
          }
          /**
           * @dev This function is only called during contract construction to create the receiver constraints
           * @dev lookup table.
           */
          function _constructReceiverConstraintsTable() internal pure returns (uint256) {
              return 
              (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_RECOMMENDED << 3))
                  | (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_ONE << 3))
                  | (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_TWO << 3))
                  | (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_THREE << 3))
                  | (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_FOUR << 3))
                  | (RECEIVER_CONSTRAINTS_NO_CODE << (TRANSFER_SECURITY_LEVEL_FIVE << 3))
                  | (RECEIVER_CONSTRAINTS_EOA << (TRANSFER_SECURITY_LEVEL_SIX << 3))
                  | (RECEIVER_CONSTRAINTS_NO_CODE << (TRANSFER_SECURITY_LEVEL_SEVEN << 3))
                  | (RECEIVER_CONSTRAINTS_EOA << (TRANSFER_SECURITY_LEVEL_EIGHT << 3))
                  | (RECEIVER_CONSTRAINTS_SBT << (TRANSFER_SECURITY_LEVEL_NINE << 3));
          }
          /*************************************************************************/
          /*                               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(uint120 id) {
              _requireCallerOwnsList(id);
              _;
          }
          /*************************************************************************/
          /*                          APPLY TRANSFER POLICIES                      */
          /*************************************************************************/
          /**
           * @notice Apply the collection transfer policy 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 The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
           *      is very deliberate.  The order of operations is determined by the most frequently used settings that are
           *      expected in the wild.
           *
           * @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
           *      are on the list of frozen accounts for the collection.
           * @dev Throws when the collection is set to Level 9 - Soulbound Token.
           * @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
           *      and the transfer is not approved by an authorizer for the collection.
           * @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
           *      isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an 
           *      authorizer for the collection..
           * @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when neither `msg.sender` nor `from` are whitelisted, if 
           *      CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer 
           *      is not approved by an authorizer for the collection.
           *
           * @dev <h4>Postconditions:</h4>
           *      1. Transfer is allowed or denied based on the applied transfer policy.
           *
           * @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(_callerAuthorizedCheckCollection, msg.sender, caller, from, to, 0);
              if (errorSelector != SELECTOR_NO_ERROR) {
                  _revertCustomErrorSelectorAsm(errorSelector);
              }
          }
          /**
           * @notice Apply the collection transfer policy 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 The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
           *      is very deliberate.  The order of operations is determined by the most frequently used settings that are
           *      expected in the wild.
           *
           * @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
           *      are on the list of frozen accounts for the collection.
           * @dev Throws when the collection is set to Level 9 - Soulbound Token.
           * @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
           *      and the transfer is not approved by an authorizer for the collection.
           * @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
           *      isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an 
           *      authorizer for the collection..
           * @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when neither `msg.sender` nor `from` are whitelisted, if 
           *      CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer 
           *      is not approved by an authorizer for the collection.
           *
           * @dev <h4>Postconditions:</h4>
           *      1. Transfer is allowed or denied based on the applied transfer policy.
           *
           * @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(_callerAuthorizedCheckToken, msg.sender, caller, from, to, tokenId);
              if (errorSelector != SELECTOR_NO_ERROR) {
                  _revertCustomErrorSelectorAsm(errorSelector);
              }
          }
          /**
           * @notice Apply the collection transfer policy 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 The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
           *      is very deliberate.  The order of operations is determined by the most frequently used settings that are
           *      expected in the wild.
           *
           * @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
           *      are on the list of frozen accounts for the collection.
           * @dev Throws when the collection is set to Level 9 - Soulbound Token.
           * @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
           *      and the transfer is not approved by an authorizer for the collection.
           * @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
           *      isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an 
           *      authorizer for the collection..
           * @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when neither `msg.sender` nor `from` are whitelisted, if 
           *      CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer 
           *      is not approved by an authorizer for the collection.
           *
           * @dev <h4>Postconditions:</h4>
           *      1. Transfer is allowed or denied based on the applied transfer policy.
           *
           * @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, uint256 /*amount*/) external {
              validateTransfer(caller, from, to, tokenId);
          }
          /**
           * @notice Apply the collection transfer policy 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 The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
           *      is very deliberate.  The order of operations is determined by the most frequently used settings that are
           *      expected in the wild.
           *
           * @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
           *      are on the list of frozen accounts for the collection.
           * @dev Throws when the collection is set to Level 9 - Soulbound Token.
           * @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
           *      and the transfer is not approved by an authorizer for the collection.
           * @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
           *      isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an 
           *      authorizer for the collection..
           * @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when neither `msg.sender` nor `from` are whitelisted, if 
           *      CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer 
           *      is not approved by an authorizer for the collection.
           *
           * @dev <h4>Postconditions:</h4>
           *      1. Transfer is allowed or denied based on the applied transfer policy.
           *
           * @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 Returns the caller and receiver constraints for the specified transfer security level.
           * 
           * @param level The transfer security level to return the caller and receiver constraints for.
           * 
           * @return callerConstraints    The `CallerConstraints` value for the level.
           * @return receiverConstraints The `ReceiverConstraints` value for the level.
           */
          function transferSecurityPolicies(
              uint256 level
          ) public view returns (uint256 callerConstraints, uint256 receiverConstraints) {
              callerConstraints = uint8((_callerConstraintsLookup >> (level << 3)));
              receiverConstraints = uint8((_receiverConstraintsLookup >> (level << 3)));
          }
          /**
           * @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.
           * 
           * @param token     The address of the token to authorize.
           * @param tokenId   The token id to set the authorized operator for.
           */
          function beforeAuthorizedTransferWithAmount(address token, uint256 tokenId, uint256 /*amount*/) external {
              _setOperatorInTransientStorage(WILDCARD_OPERATOR_ADDRESS, 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 afterAuthorizedTransferWithAmount(address token, uint256 tokenId) external {
              afterAuthorizedTransfer(token, tokenId);
          }
          /*************************************************************************/
          /*                              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 returns (uint120 id) {
              unchecked {
                  id = ++lastListId;
              }
              listOwners[id] = msg.sender;
              emit CreatedList(id, name);
              emit ReassignedListOwnership(id, msg.sender);
          }
          /**
           * @notice Creates a new list id, and copies all blacklisted and whitelisted 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, uint120 sourceListId) external returns (uint120 id) {
              unchecked {
                  id = ++lastListId;
              }
              unchecked {
                  if (sourceListId > id - 1) {
                      revert CreatorTokenTransferValidator__ListDoesNotExist();
                  }
              }
              listOwners[id] = msg.sender;
              emit CreatedList(id, name);
              emit ReassignedListOwnership(id, msg.sender);
              List storage sourceBlacklist = blacklists[sourceListId];
              List storage sourceWhitelist = whitelists[sourceListId];
              List storage sourceAuthorizers = authorizers[sourceListId];
              List storage targetBlacklist = blacklists[id];
              List storage targetWhitelist = whitelists[id];
              List storage targetAuthorizers = authorizers[id];
              _copyAddressSet(LIST_TYPE_BLACKLIST, id, sourceBlacklist, targetBlacklist);
              _copyBytes32Set(LIST_TYPE_BLACKLIST, id, sourceBlacklist, targetBlacklist);
              _copyAddressSet(LIST_TYPE_WHITELIST, id, sourceWhitelist, targetWhitelist);
              _copyBytes32Set(LIST_TYPE_WHITELIST, id, sourceWhitelist, targetWhitelist);
              _copyAddressSet(LIST_TYPE_AUTHORIZERS, id, sourceAuthorizers, targetAuthorizers);
              _copyBytes32Set(LIST_TYPE_AUTHORIZERS, id, sourceAuthorizers, targetAuthorizers);
          }
          /**
           * @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(uint120 id, address newOwner) public {
              if(newOwner == address(0)) {
                  revert CreatorTokenTransferValidator__ListOwnershipCannotBeTransferredToZeroAddress();
              }
              _reassignOwnershipOfList(id, newOwner);
          }
          /**
           * @notice Renounce the ownership of a list, rendering the list immutable.
           *
           * @dev Throws when the caller does not own the specified 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(uint120 id) public {
              _reassignOwnershipOfList(id, address(0));
          }
          /**
           * @notice Set the transfer security level, authorization mode and account freezing mode settings 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 transfer security level of the specified collection is set to the new value.
           *      2. The authorization mode setting of the specified collection is set to the new value.
           *      3. The authorization wildcard operator mode setting of the specified collection is set to the new value.
           *      4. The account freezing mode setting of the specified collection is set to the new value.
           *      5. A `SetTransferSecurityLevel` event is emitted.
           *      6. A `SetAuthorizationModeEnabled` event is emitted.
           *      7. A `SetAccountFreezingModeEnabled` event is emitted.
           *
           * @param collection                 The address of the collection.
           * @param level                      The new transfer security level to apply.
           * @param disableAuthorizationMode   Flag if the collection allows for authorizer mode.
           * @param disableWildcardOperators   Flag if the authorizer can set wildcard operators.
           * @param enableAccountFreezingMode  Flag if the collection is using account freezing.
           */
          function setTransferSecurityLevelOfCollection(
              address collection, 
              uint8 level,
              bool disableAuthorizationMode,
              bool disableWildcardOperators,
              bool enableAccountFreezingMode) external {
              if (level > TRANSFER_SECURITY_LEVEL_NINE) {
                  revert CreatorTokenTransferValidator__InvalidTransferSecurityLevel();
              }
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              collectionSecurityPolicies[collection].transferSecurityLevel = level;
              collectionSecurityPolicies[collection].disableAuthorizationMode = disableAuthorizationMode;
              collectionSecurityPolicies[collection].authorizersCannotSetWildcardOperators = disableWildcardOperators;
              collectionSecurityPolicies[collection].enableAccountFreezingMode = enableAccountFreezingMode;
              emit SetTransferSecurityLevel(collection, level);
              emit SetAuthorizationModeEnabled(collection, disableAuthorizationMode, disableWildcardOperators);
              emit SetAccountFreezingModeEnabled(collection, enableAccountFreezingMode);
          }
          /**
           * @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 {
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              collectionSecurityPolicies[collection].tokenType = tokenType;
              emit SetTokenType(collection, tokenType);
          }
          /**
           * @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, uint120 id) public {
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              if (id > lastListId) {
                  revert CreatorTokenTransferValidator__ListDoesNotExist();
              }
              collectionSecurityPolicies[collection].listId = id;
              emit AppliedListToCollection(collection, id);
          }
          /**
           * @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 {
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              AccountList storage accounts = frozenAccounts[collection];
              for (uint256 i = 0; i < accountsToFreeze.length;) {
                  address accountToFreeze = accountsToFreeze[i];
                  if (accounts.enumerableAccounts.add(accountToFreeze)) {
                      emit AccountFrozenForCollection(collection, accountToFreeze);
                      accounts.nonEnumerableAccounts[accountToFreeze] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @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 {
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              AccountList storage accounts = frozenAccounts[collection];
              for (uint256 i = 0; i < accountsToUnfreeze.length;) {
                  address accountToUnfreeze = accountsToUnfreeze[i];
                  if (accounts.enumerableAccounts.remove(accountToUnfreeze)) {
                      emit AccountUnfrozenForCollection(collection, accountToUnfreeze);
                      accounts.nonEnumerableAccounts[accountToUnfreeze] = false;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @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:
           *                   Transfer security level, operator whitelist id, permitted contract receiver allowlist id,
           *                   authorizer mode, if authorizer can set a wildcard operator, and if account freezing is
           *                   enabled.
           */
          function getCollectionSecurityPolicy(
              address collection
          ) external view returns (CollectionSecurityPolicyV3 memory) {
              return collectionSecurityPolicies[collection];
          }
          /**
           * @notice Adds one or more accounts to a blacklist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the accounts array is empty.
           *
           * @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 accounts The addresses of the accounts to add.
           */
          function addAccountsToBlacklist(
              uint120 id, 
              address[] calldata accounts
          ) external {
              _addAccountsToList(blacklists[id], LIST_TYPE_BLACKLIST, id, accounts);
          }
          /**
           * @notice Adds one or more accounts to a whitelist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the accounts array is empty.
           *
           * @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 accounts The addresses of the accounts to add.
           */
          function addAccountsToWhitelist(
              uint120 id, 
              address[] calldata accounts
          ) external {
              _addAccountsToList(whitelists[id], LIST_TYPE_WHITELIST, id, accounts);
          }
          /**
           * @notice Adds one or more accounts to authorizers.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the accounts array is empty.
           *
           * @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 accounts The addresses of the accounts to add.
           */
          function addAccountsToAuthorizers(
              uint120 id, 
              address[] calldata accounts
          ) external {
              _addAccountsToList(authorizers[id], LIST_TYPE_AUTHORIZERS, id, accounts);
          }
          /**
           * @notice Adds one or more codehashes to a blacklist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the codehashes array is empty.
           * @dev Throws when a codehash is zero.
           *
           * @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 codehashes The codehashes to add.
           */
          function addCodeHashesToBlacklist(
              uint120 id, 
              bytes32[] calldata codehashes
          ) external {
              _addCodeHashesToList(blacklists[id], LIST_TYPE_BLACKLIST, id, codehashes);
          }
          /**
           * @notice Adds one or more codehashes to a whitelist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the codehashes array is empty.
           * @dev Throws when a codehash is zero.
           *
           * @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 codehashes The codehashes to add.
           */
          function addCodeHashesToWhitelist(
              uint120 id, 
              bytes32[] calldata codehashes
          ) external {
              _addCodeHashesToList(whitelists[id], LIST_TYPE_WHITELIST, id, codehashes);
          }
          /**
           * @notice Removes one or more accounts from a blacklist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the accounts array is empty.
           *
           * @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 accounts The addresses of the accounts to remove.
           */
          function removeAccountsFromBlacklist(
              uint120 id, 
              address[] calldata accounts
          ) external {
              _removeAccountsFromList(blacklists[id], LIST_TYPE_BLACKLIST, id, accounts);
          }
          /**
           * @notice Removes one or more accounts from a whitelist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the accounts array is empty.
           *
           * @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 accounts The addresses of the accounts to remove.
           */
          function removeAccountsFromWhitelist(
              uint120 id, 
              address[] calldata accounts
          ) external {
              _removeAccountsFromList(whitelists[id], LIST_TYPE_WHITELIST, id, accounts);
          }
          /**
           * @notice Removes one or more accounts from authorizers.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the accounts array is empty.
           *
           * @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 accounts The addresses of the accounts to remove.
           */
          function removeAccountsFromAuthorizers(
              uint120 id, 
              address[] calldata accounts
          ) external {
              _removeAccountsFromList(authorizers[id], LIST_TYPE_AUTHORIZERS, id, accounts);
          }
          /**
           * @notice Removes one or more codehashes from a blacklist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the codehashes array is empty.
           *
           * @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 codehashes The codehashes to remove.
           */
          function removeCodeHashesFromBlacklist(
              uint120 id, 
              bytes32[] calldata codehashes
          ) external {
              _removeCodeHashesFromList(blacklists[id], LIST_TYPE_BLACKLIST, id, codehashes);
          }
          /**
           * @notice Removes one or more codehashes from a whitelist.
           *
           * @dev Throws when the caller does not own the specified list.
           * @dev Throws when the codehashes array is empty.
           *
           * @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 codehashes The codehashes to remove.
           */
          function removeCodeHashesFromWhitelist(
              uint120 id, 
              bytes32[] calldata codehashes
          ) external {
              _removeCodeHashesFromList(whitelists[id], LIST_TYPE_WHITELIST, id, codehashes);
          }
          /**
           * @notice Get blacklisted accounts by list id.
           * @param  id The id of the list.
           * @return An array of blacklisted accounts.
           */
          function getBlacklistedAccounts(uint120 id) public view returns (address[] memory) {
              return blacklists[id].enumerableAccounts.values();
          }
          /**
           * @notice Get whitelisted accounts by list id.
           * @param  id The id of the list.
           * @return An array of whitelisted accounts.
           */
          function getWhitelistedAccounts(uint120 id) public view returns (address[] memory) {
              return whitelists[id].enumerableAccounts.values();
          }
          /**
           * @notice Get authorizor accounts by list id.
           * @param  id The id of the list.
           * @return An array of authorizer accounts.
           */
          function getAuthorizerAccounts(uint120 id) public view returns (address[] memory) {
              return authorizers[id].enumerableAccounts.values();
          }
          /**
           * @notice Get blacklisted codehashes by list id.
           * @param id The id of the list.
           * @return   An array of blacklisted codehashes.
           */
          function getBlacklistedCodeHashes(uint120 id) public view returns (bytes32[] memory) {
              return blacklists[id].enumerableCodehashes.values();
          }
          /**
           * @notice Get whitelisted codehashes by list id.
           * @param id The id of the list.
           * @return   An array of whitelisted codehashes.
           */
          function getWhitelistedCodeHashes(uint120 id) public view returns (bytes32[] memory) {
              return whitelists[id].enumerableCodehashes.values();
          }
          /**
           * @notice Check if an account is blacklisted in a specified list.
           * @param id       The id of the list.
           * @param account  The address of the account to check.
           * @return         True if the account is blacklisted in the specified list, false otherwise.
           */
          function isAccountBlacklisted(uint120 id, address account) public view returns (bool) {
              return blacklists[id].nonEnumerableAccounts[account];
          }
          /**
           * @notice Check if an account is whitelisted in a specified list.
           * @param id       The id of the list.
           * @param account  The address of the account to check.
           * @return         True if the account is whitelisted in the specified list, false otherwise.
           */
          function isAccountWhitelisted(uint120 id, address account) public view returns (bool) {
              return whitelists[id].nonEnumerableAccounts[account];
          }
          /**
           * @notice Check if an account is an authorizer in a specified list.
           * @param id       The id of the list.
           * @param account  The address of the account to check.
           * @return         True if the account is an authorizer in the specified list, false otherwise.
           */
          function isAccountAuthorizer(uint120 id, address account) public view returns (bool) {
              return authorizers[id].nonEnumerableAccounts[account];
          }
          /**
           * @notice Check if a codehash is blacklisted in a specified list.
           * @param id       The id of the list.
           * @param codehash  The codehash to check.
           * @return         True if the codehash is blacklisted in the specified list, false otherwise.
           */
          function isCodeHashBlacklisted(uint120 id, bytes32 codehash) public view returns (bool) {
              return blacklists[id].nonEnumerableCodehashes[codehash];
          }
          /**
           * @notice Check if a codehash is whitelisted in a specified list.
           * @param id       The id of the list.
           * @param codehash  The codehash to check.
           * @return         True if the codehash is whitelisted in the specified list, false otherwise.
           */
          function isCodeHashWhitelisted(uint120 id, bytes32 codehash) public view returns (bool) {
              return whitelists[id].nonEnumerableCodehashes[codehash];
          }
          /**
           * @notice Get blacklisted accounts by collection.
           * @param collection The address of the collection.
           * @return           An array of blacklisted accounts.
           */
          function getBlacklistedAccountsByCollection(address collection) external view returns (address[] memory) {
              return getBlacklistedAccounts(collectionSecurityPolicies[collection].listId);
          }
          /**
           * @notice Get whitelisted accounts by collection.
           * @param collection The address of the collection.
           * @return           An array of whitelisted accounts.
           */
          function getWhitelistedAccountsByCollection(address collection) external view returns (address[] memory) {
              return getWhitelistedAccounts(collectionSecurityPolicies[collection].listId);
          }
          /**
           * @notice Get authorizer accounts by collection.
           * @param collection The address of the collection.
           * @return           An array of authorizer accounts.
           */
          function getAuthorizerAccountsByCollection(address collection) external view returns (address[] memory) {
              return getAuthorizerAccounts(collectionSecurityPolicies[collection].listId);
          }
          /**
           * @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 frozenAccounts[collection].enumerableAccounts.values();
          }
          /**
           * @notice Get blacklisted codehashes by collection.
           * @param collection The address of the collection.
           * @return           An array of blacklisted codehashes.
           */
          function getBlacklistedCodeHashesByCollection(address collection) external view returns (bytes32[] memory) {
              return getBlacklistedCodeHashes(collectionSecurityPolicies[collection].listId);
          }
          /**
           * @notice Get whitelisted codehashes by collection.
           * @param collection The address of the collection.
           * @return           An array of whitelisted codehashes.
           */
          function getWhitelistedCodeHashesByCollection(address collection) external view returns (bytes32[] memory) {
              return getWhitelistedCodeHashes(collectionSecurityPolicies[collection].listId);
          }
          /**
           * @notice Check if an account is blacklisted by 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 blacklisted by the specified collection, false otherwise.
           */
          function isAccountBlacklistedByCollection(address collection, address account) external view returns (bool) {
              return isAccountBlacklisted(collectionSecurityPolicies[collection].listId, account);
          }
          /**
           * @notice Check if an account is whitelisted by 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 whitelisted by the specified collection, false otherwise.
           */
          function isAccountWhitelistedByCollection(address collection, address account) external view returns (bool) {
              return isAccountWhitelisted(collectionSecurityPolicies[collection].listId, account);
          }
          /**
           * @notice Check if an account is an authorizer of 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 an authorizer by the specified collection, false otherwise.
           */
          function isAccountAuthorizerOfCollection(address collection, address account) external view returns (bool) {
              return isAccountAuthorizer(collectionSecurityPolicies[collection].listId, account);
          }
          /**
           * @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 frozenAccounts[collection].nonEnumerableAccounts[account];
          }
          /**
           * @notice Check if a codehash is blacklisted by a specified collection.
           * @param collection The address of the collection.
           * @param codehash   The codehash to check.
           * @return           True if the codehash is blacklisted by the specified collection, false otherwise.
           */
          function isCodeHashBlacklistedByCollection(address collection, bytes32 codehash) external view returns (bool) {
              return isCodeHashBlacklisted(collectionSecurityPolicies[collection].listId, codehash);
          }
          /**
           * @notice Check if a codehash is whitelisted by a specified collection.
           * @param collection The address of the collection.
           * @param codehash   The codehash to check.
           * @return           True if the codehash is whitelisted by the specified collection, false otherwise.
           */
          function isCodeHashWhitelistedByCollection(address collection, bytes32 codehash) external view returns (bool) {
              return isCodeHashWhitelisted(collectionSecurityPolicies[collection].listId, codehash);
          }
          /// @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 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                                */
          /*************************************************************************/
          /**
           * @notice Reverts the transaction if the caller is not the owner or assigned the default
           * @notice admin role of the contract at `tokenAddress`.
           *
           * @dev    Throws when the caller is neither owner nor assigned the default admin role.
           * 
           * @param tokenAddress The contract address of the token to check permissions for.
           */
          function _requireCallerIsNFTOrContractOwnerOrAdmin(address tokenAddress) internal view {
              address caller = msg.sender;
              
              if(caller == tokenAddress) {
                  return;
              }
              (address contractOwner,) = _safeOwner(tokenAddress);
              if(caller == contractOwner) {
                  return;
              }
              (bool callerIsContractAdmin,) = _safeHasRole(tokenAddress, DEFAULT_ACCESS_CONTROL_ADMIN_ROLE, caller);
              if(callerIsContractAdmin) {
                  return;
              }
              revert CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
          }
          /**
           * @notice Copies all addresses in `ptrFromList` to `ptrToList`.
           * 
           * @dev    This function will copy all addresses from one list to another list.
           * @dev    Note: If used to copy adddresses to an existing list the current list contents will not be
           * @dev    deleted before copying. New addresses will be appeneded to the end of the list and the
           * @dev    non-enumerable mapping key value will be set to true.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Addresses in from list that are not already present in to list are added to the to list.
           *      2. Emits an `AddedAccountToList` event for each address copied to the list.
           * 
           * @param  listType          The type of list addresses are being copied from and to.
           * @param  destinationListId The id of the list being copied to.
           * @param  ptrFromList       The storage pointer for the list being copied from.
           * @param  ptrToList         The storage pointer for the list being copied to.
           */
          function _copyAddressSet(
              uint8 listType,
              uint120 destinationListId,
              List storage ptrFromList,
              List storage ptrToList
          ) private {
              EnumerableSet.AddressSet storage ptrFromSet = ptrFromList.enumerableAccounts;
              EnumerableSet.AddressSet storage ptrToSet = ptrToList.enumerableAccounts;
              mapping (address => bool) storage ptrToNonEnumerableSet = ptrToList.nonEnumerableAccounts;
              uint256 sourceLength = ptrFromSet.length();
              address account;
              for (uint256 i = 0; i < sourceLength;) {
                  account = ptrFromSet.at(i); 
                  if (ptrToSet.add(account)) {
                      emit AddedAccountToList(listType, destinationListId, account);
                      ptrToNonEnumerableSet[account] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Copies all codehashes in `ptrFromList` to `ptrToList`.
           * 
           * @dev    This function will copy all codehashes from one list to another list.
           * @dev    Note: If used to copy codehashes to an existing list the current list contents will not be
           * @dev    deleted before copying. New codehashes will be appeneded to the end of the list and the
           * @dev    non-enumerable mapping key value will be set to true.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Codehashes in from list that are not already present in to list are added to the to list.
           *      2. Emits an `AddedCodeHashToList` event for each codehash copied to the list.
           * 
           * @param  listType          The type of list codehashes are being copied from and to.
           * @param  destinationListId The id of the list being copied to.
           * @param  ptrFromList       The storage pointer for the list being copied from.
           * @param  ptrToList         The storage pointer for the list being copied to.
           */
          function _copyBytes32Set(
              uint8 listType,
              uint120 destinationListId,
              List storage ptrFromList,
              List storage ptrToList
          ) private {
              EnumerableSet.Bytes32Set storage ptrFromSet = ptrFromList.enumerableCodehashes;
              EnumerableSet.Bytes32Set storage ptrToSet = ptrToList.enumerableCodehashes;
              mapping (bytes32 => bool) storage ptrToNonEnumerableSet = ptrToList.nonEnumerableCodehashes;
              uint256 sourceLength = ptrFromSet.length();
              bytes32 codehash;
              for (uint256 i = 0; i < sourceLength;) {
                  codehash = ptrFromSet.at(i);
                  if (ptrToSet.add(codehash)) {
                      emit AddedCodeHashToList(listType, destinationListId, codehash);
                      ptrToNonEnumerableSet[codehash] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Adds one or more accounts to a list.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Accounts that were not previously in the list are added to the list.
           *      2. An `AddedAccountToList` event is emitted for each account that was not
           *         previously on the list.
           * 
           * @param list     The storage pointer for the list to add accounts to.
           * @param listType The type of list the accounts are being added to.
           * @param id       The id of the list the accounts are being added to.
           * @param accounts An array of accounts to add to the list.
           */
          function _addAccountsToList(
              List storage list,
              uint8 listType,
              uint120 id,
              address[] calldata accounts
          ) internal onlyListOwner(id) {
              address account;
              for (uint256 i = 0; i < accounts.length;) {
                  account = accounts[i];
                  if (list.enumerableAccounts.add(account)) {
                      emit AddedAccountToList(listType, id, account);
                      list.nonEnumerableAccounts[account] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Adds one or more codehashes to a list.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Codehashes that were not previously in the list are added to the list.
           *      2. An `AddedCodeHashToList` event is emitted for each codehash that was not
           *         previously on the list.
           * 
           * @param list        The storage pointer for the list to add codehashes to.
           * @param listType    The type of list the codehashes are being added to.
           * @param id          The id of the list the codehashes are being added to.
           * @param codehashes  An array of codehashes to add to the list.
           */
          function _addCodeHashesToList(
              List storage list,
              uint8 listType,
              uint120 id,
              bytes32[] calldata codehashes
          ) internal onlyListOwner(id) {
              bytes32 codehash;
              for (uint256 i = 0; i < codehashes.length;) {
                  codehash = codehashes[i];
                  if (list.enumerableCodehashes.add(codehash)) {
                      emit AddedCodeHashToList(listType, id, codehash);
                      list.nonEnumerableCodehashes[codehash] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Removes one or more accounts from a list.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Accounts that were previously in the list are removed from the list.
           *      2. An `RemovedAccountFromList` event is emitted for each account that was
           *         previously on the list.
           * 
           * @param list        The storage pointer for the list to remove accounts from.
           * @param listType    The type of list the accounts are being removed from.
           * @param id          The id of the list the accounts are being removed from.
           * @param accounts    An array of accounts to remove from the list.
           */
          function _removeAccountsFromList(
              List storage list, 
              uint8 listType,
              uint120 id, 
              address[] memory accounts
          ) internal onlyListOwner(id) {
              address account;
              for (uint256 i = 0; i < accounts.length;) {
                  account = accounts[i];
                  if (list.enumerableAccounts.remove(account)) {
                      emit RemovedAccountFromList(listType, id, account);
                      delete list.nonEnumerableAccounts[account];
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Removes one or more codehashes from a list.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Codehashes that were previously in the list are removed from the list.
           *      2. An `RemovedCodeHashFromList` event is emitted for each codehash that was
           *         previously on the list.
           * 
           * @param list        The storage pointer for the list to remove codehashes from.
           * @param listType    The type of list the codehashes are being removed from.
           * @param id          The id of the list the codehashes are being removed from.
           * @param codehashes  An array of codehashes to remove from the list.
           */
          function _removeCodeHashesFromList(
              List storage list, 
              uint8 listType, 
              uint120 id, 
              bytes32[] calldata codehashes
          ) internal onlyListOwner(id) {
              bytes32 codehash;
              for (uint256 i = 0; i < codehashes.length;) {
                  codehash = codehashes[i];
                  if (list.enumerableCodehashes.remove(codehash)) {
                      emit RemovedCodeHashFromList(listType, id, codehash);
                      delete list.nonEnumerableCodehashes[codehash];
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Sets the owner of list `id` to `newOwner`.
           * 
           * @dev    Throws when the caller is not the owner of the list.
           * 
           * @dev    <h4>Postconditions:</h4>
           *         1. The owner of list `id` is set to `newOwner`.
           *         2. Emits a `ReassignedListOwnership` event.
           * 
           * @param id       The id of the list to reassign ownership of.
           * @param newOwner The account to assign ownership of the list to.
           */
          function _reassignOwnershipOfList(uint120 id, address newOwner) private {
              _requireCallerOwnsList(id);
              listOwners[id] = newOwner;
              emit ReassignedListOwnership(id, newOwner);
          }
          /**
           * @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(uint120 id) private view {
              if (msg.sender != listOwners[id]) {
                  revert CreatorTokenTransferValidator__CallerDoesNotOwnList();
              }
          }
          /**
           * @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) }
          }
          /**
           * @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(_callerAuthorizedCheckToken, token, msg.sender, from, to, id);
              if (collectionTokenType == DEFAULT_TOKEN_TYPE || collectionTokenType == tokenType) {
                  isError = SELECTOR_NO_ERROR != selector;
              } else {
                  revert CreatorTokenTransferValidator__TokenTypesDoNotMatch();
              }
          }
          /**
           * @notice Apply the collection transfer policy 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 The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
           *      is very deliberate.  The order of operations is determined by the most frequently used settings that are
           *      expected in the wild.
           *
           * @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
           *      are on the list of frozen accounts for the collection.
           * @dev Throws when the collection is set to Level 9 - Soulbound Token.
           * @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
           *      and the transfer is not approved by an authorizer for the collection.
           * @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
           *      isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an 
           *      authorizer for the collection..
           * @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
           *      `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
           * @dev Throws when neither `msg.sender` nor `from` are whitelisted, if 
           *      CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer 
           *      is not approved by an authorizer for the collection.
           *
           * @dev <h4>Postconditions:</h4>
           *      1. Transfer is allowed or denied based on the applied transfer policy.
           *
           * @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(
              function(address,address,uint256) internal view returns(bool) _callerAuthorizedParam,
              address collection, 
              address caller, 
              address from, 
              address to,
              uint256 tokenId
          ) 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);
              }
              CollectionSecurityPolicyV3 storage collectionSecurityPolicy = collectionSecurityPolicies[collection];
              uint120 listId = collectionSecurityPolicy.listId;
              (uint256 callerConstraints, uint256 receiverConstraints) = 
                  transferSecurityPolicies(collectionSecurityPolicy.transferSecurityLevel);
              if (collectionSecurityPolicy.enableAccountFreezingMode) {
                  AccountList storage frozenAccountList = frozenAccounts[collection];
                  
                  if (frozenAccountList.nonEnumerableAccounts[from]) {
                      return (CreatorTokenTransferValidator__SenderAccountIsFrozen.selector, DEFAULT_TOKEN_TYPE);
                  }
                  if (frozenAccountList.nonEnumerableAccounts[to]) {
                      return (CreatorTokenTransferValidator__ReceiverAccountIsFrozen.selector, DEFAULT_TOKEN_TYPE);
                  }
              }
              if (callerConstraints == CALLER_CONSTRAINTS_SBT) {
                  return (CreatorTokenTransferValidator__TokenIsSoulbound.selector, DEFAULT_TOKEN_TYPE);
              }
              List storage whitelist = whitelists[listId];
              if (receiverConstraints == RECEIVER_CONSTRAINTS_NO_CODE) {
                  if (_getCodeLengthAsm(to) > 0) {
                      if (!whitelist.nonEnumerableAccounts[to]) {
                          // Cache _callerAuthorizedParam on stack to avoid stack too deep
                          function(address,address,uint256) internal view returns(bool) _callerAuthorized = _callerAuthorizedParam;
                          if(!_callerAuthorized(collection, caller, tokenId)) {
                              if (!whitelist.nonEnumerableCodehashes[_getCodeHashAsm(to)]) {
                                  return (CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode.selector, DEFAULT_TOKEN_TYPE);
                              }
                          }
                      }
                  }
              } else if (receiverConstraints == RECEIVER_CONSTRAINTS_EOA) {
                  if (!isVerifiedEOA(to)) {
                      if (!whitelist.nonEnumerableAccounts[to]) {
                          // Cache _callerAuthorizedParam on stack to avoid stack too deep
                          function(address,address,uint256) internal view returns(bool) _callerAuthorized = _callerAuthorizedParam;
                          if(!_callerAuthorized(collection, caller, tokenId)) {
                              if (!whitelist.nonEnumerableCodehashes[_getCodeHashAsm(to)]) {
                                  return (CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified.selector, DEFAULT_TOKEN_TYPE);
                              }
                          }
                      }
                  }
              }
              if (caller == from) {
                  if (callerConstraints != CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
              }
              if (callerConstraints == CALLER_CONSTRAINTS_OPERATOR_BLACKLIST_ENABLE_OTC) {
                  // Cache _callerAuthorizedParam on stack to avoid stack too deep
                  function(address,address,uint256) internal view returns(bool) _callerAuthorized = _callerAuthorizedParam;
                  if(_callerAuthorized(collection, caller, tokenId)) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  List storage blacklist = blacklists[listId];
                  if (blacklist.nonEnumerableAccounts[caller]) {
                      return (CreatorTokenTransferValidator__OperatorIsBlacklisted.selector, DEFAULT_TOKEN_TYPE);
                  }
                  if (blacklist.nonEnumerableCodehashes[_getCodeHashAsm(caller)]) {
                      return (CreatorTokenTransferValidator__OperatorIsBlacklisted.selector, DEFAULT_TOKEN_TYPE);
                  }
              } else if (callerConstraints == CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC) {
                  if (whitelist.nonEnumerableAccounts[caller]) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  // Cache _callerAuthorizedParam on stack to avoid stack too deep
                  function(address,address,uint256) internal view returns(bool) _callerAuthorized = _callerAuthorizedParam;
                  if( _callerAuthorized(collection, caller, tokenId)) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  if (whitelist.nonEnumerableCodehashes[_getCodeHashAsm(caller)]) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  return (CreatorTokenTransferValidator__CallerMustBeWhitelisted.selector, DEFAULT_TOKEN_TYPE);
              } else if (callerConstraints == CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC) {
                  mapping(address => bool) storage accountWhitelist = whitelist.nonEnumerableAccounts;
                  if (accountWhitelist[caller]) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  if (accountWhitelist[from]) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  // Cache _callerAuthorizedParam on stack to avoid stack too deep
                  function(address,address,uint256) internal view returns(bool) _callerAuthorized = _callerAuthorizedParam;
                  if(_callerAuthorized(collection, caller, tokenId)) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  mapping(bytes32 => bool) storage codehashWhitelist = whitelist.nonEnumerableCodehashes;
                  // Cache caller on stack to avoid stack too deep
                  address tmpAddress = caller;
                  if (codehashWhitelist[_getCodeHashAsm(tmpAddress)]) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  // Cache from on stack to avoid stack too deep
                  tmpAddress = from;
                  if (codehashWhitelist[_getCodeHashAsm(tmpAddress)]) {
                      return (SELECTOR_NO_ERROR, collectionSecurityPolicy.tokenType);
                  }
                  return (CreatorTokenTransferValidator__CallerMustBeWhitelisted.selector, DEFAULT_TOKEN_TYPE);
              }
              return (SELECTOR_NO_ERROR, collectionSecurityPolicy.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 {
              CollectionSecurityPolicyV3 storage collectionSecurityPolicy = collectionSecurityPolicies[collection];
              if (collectionSecurityPolicy.disableAuthorizationMode) {
                  revert CreatorTokenTransferValidator__AuthorizationDisabledForCollection();
              }
              if (collectionSecurityPolicy.authorizersCannotSetWildcardOperators) {
                  if (operator == WILDCARD_OPERATOR_ADDRESS) {
                      revert CreatorTokenTransferValidator__WildcardOperatorsCannotBeAuthorizedForCollection();
                  }
              }
              if (!authorizers[collectionSecurityPolicy.listId].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 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
          ) 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*/
          ) 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 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 pure returns (uint256 operatorSlot) {
              return uint256(uint160(collection));
          }
          /**
           * @dev A gas efficient, and fallback-safe way to call the owner function on a token contract.
           *      This will get the owner if it exists - and when the function is unimplemented, the
           *      presence of a fallback function will not result in halted execution.
           * 
           * @param tokenAddress  The address of the token collection to get the owner of.
           * 
           * @return owner   The owner of the token collection contract.
           * @return isError True if there was an error in retrieving the owner, false if the call was successful.
           */
          function _safeOwner(
              address tokenAddress
          ) internal view returns(address owner, bool isError) {
              assembly {
                  function _callOwner(_tokenAddress) -> _owner, _isError {
                      mstore(0x00, 0x8da5cb5b)
                      if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _tokenAddress, 0x1C, 0x04, 0x00, 0x20)) {
                          _owner := mload(0x00)
                          leave
                      }
                      _isError := true
                  }
                  owner, isError := _callOwner(tokenAddress)
              }
          }
          
          /**
           * @dev A gas efficient, and fallback-safe way to call the hasRole function on a token contract.
           *      This will check if the account `hasRole` if `hasRole` exists - and when the function is unimplemented, the
           *      presence of a fallback function will not result in halted execution.
           * 
           * @param tokenAddress  The address of the token collection to call hasRole on.
           * @param role          The role to check if the account has on the collection.
           * @param account       The address of the account to check if they have a specified role.
           * 
           * @return hasRole The owner of the token collection contract.
           * @return isError True if there was an error in retrieving the owner, false if the call was successful.
           */
          function _safeHasRole(
              address tokenAddress,
              bytes32 role,
              address account
          ) internal view returns(bool hasRole, bool isError) {
              assembly {
                  function _callHasRole(_tokenAddress, _role, _account) -> _hasRole, _isError {
                      let ptr := mload(0x40)
                      mstore(0x40, add(ptr, 0x60))
                      mstore(ptr, 0x91d14854)
                      mstore(add(0x20, ptr), _role)
                      mstore(add(0x40, ptr), _account)
                      if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _tokenAddress, add(ptr, 0x1C), 0x44, 0x00, 0x20)) {
                          _hasRole := mload(0x00)
                          leave
                      }
                      _isError := true
                  }
                  hasRole, isError := _callHasRole(tokenAddress, role, account)
              }
          }
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      /**
       * @dev Constant definitions for receiver constraints used by the transfer validator.
       */
      /// @dev No constraints on the receiver of a token.
      uint256 constant RECEIVER_CONSTRAINTS_NONE = 0;
      /// @dev Token receiver cannot have deployed code.
      uint256 constant RECEIVER_CONSTRAINTS_NO_CODE = 1;
      /// @dev Token receiver must be a verified EOA with the EOA Registry.
      uint256 constant RECEIVER_CONSTRAINTS_EOA = 2;
      /// @dev Token is a soulbound token and cannot be transferred.
      uint256 constant RECEIVER_CONSTRAINTS_SBT = 3;
      /**
       * @dev Constant definitions for caller constraints used by the transfer validator.
       */
      /// @dev No constraints on the caller of a token transfer.
      uint256 constant CALLER_CONSTRAINTS_NONE = 0;
      /// @dev Caller of a token transfer must not be on the blacklist unless it is an OTC transfer.
      uint256 constant CALLER_CONSTRAINTS_OPERATOR_BLACKLIST_ENABLE_OTC = 1;
      /// @dev Caller of a token transfer must be on the whitelist unless it is an OTC transfer.
      uint256 constant CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC = 2;
      /// @dev Caller of a token transfer must be on the whitelist.
      uint256 constant CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC = 3;
      /// @dev Token is a soulbound token and cannot be transferred.
      uint256 constant CALLER_CONSTRAINTS_SBT = 4;
      /**
       * @dev Constant definitions for transfer security levels used by the transfer validator
       *      to define what receiver and caller constraints are applied to a transfer.
       */
      /// @dev Recommend Security Level -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: None
      ///        OTC: Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_RECOMMENDED = 0;
      /// @dev Security Level One -
      ///        Caller Constraints: None
      ///        Receiver Constraints: None
      ///        OTC: Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_ONE = 1;
      /// @dev Security Level Two -
      ///        Caller Constraints: Operator Blacklist
      ///        Receiver Constraints: None
      ///        OTC: Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_TWO = 2;
      /// @dev Security Level Three -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: None
      ///        OTC: Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_THREE = 3;
      /// @dev Security Level Four -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: None
      ///        OTC: Not Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_FOUR = 4;
      /// @dev Security Level Five -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: No Code
      ///        OTC: Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_FIVE = 5;
      /// @dev Security Level Six -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: Verified EOA
      ///        OTC: Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_SIX = 6;
      /// @dev Security Level Seven -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: No Code
      ///        OTC: Not Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_SEVEN = 7;
      /// @dev Security Level Eight -
      ///        Caller Constraints: Operator Whitelist
      ///        Receiver Constraints: Verified EOA
      ///        OTC: Not Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_EIGHT = 8;
      /// @dev Security Level Nine -
      ///        Soulbound Token, No Transfers Allowed
      uint8 constant TRANSFER_SECURITY_LEVEL_NINE = 9;
      /// @dev List type is a blacklist.
      uint8 constant LIST_TYPE_BLACKLIST = 0;
      /// @dev List type is a whitelist.
      uint8 constant LIST_TYPE_WHITELIST = 1;
      /// @dev List type is authorizers.
      uint8 constant LIST_TYPE_AUTHORIZERS = 2;
      /// @dev Constant value for the no error selector.
      bytes4 constant SELECTOR_NO_ERROR = bytes4(0x00000000);// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
      interface IEOARegistry is IERC165 {
          function isVerifiedEOA(address account) external view returns (bool);
      }// SPDX-License-Identifier: MIT
      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.4;
      /**
       * @dev Defines constraints for staking tokens in token wrapper contracts.
       */
      enum StakerConstraints {
          // 0: No constraints applied to staker.
          None,
          // 1: Transaction originator must be the address that will receive the wrapped tokens.
          CallerIsTxOrigin,
          // 2: Address that will receive the wrapped tokens must be a verified EOA.
          EOA
      }
      /**
       * @dev Defines the security policy for a token collection in Creator Token Standards V2.
       * 
       * @dev **transferSecurityLevel**: The transfer security level set for the collection.
       * @dev **listId**: The list id that contains the blacklist and whitelist to apply to the collection.
       * @dev **enableGraylisting**: If true, graylisting will be enabled for the collection.
       */
      struct CollectionSecurityPolicyV3 {
          bool disableAuthorizationMode;
          bool authorizersCannotSetWildcardOperators;
          uint8 transferSecurityLevel;
          uint120 listId;
          bool enableAccountFreezingMode;
          uint16 tokenType;
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.24;
      import "@openzeppelin/contracts/access/Ownable.sol";
      contract CreatorTokenTransferValidatorConfiguration is Ownable {
          error CreatorTokenTransferValidatorConfiguration__ConfigurationNotInitialized();
          bool configurationInitialized;
          uint256 private nativeValueToCheckPauseState;
          constructor(address defaultOwner) {
              _transferOwnership(defaultOwner);
          }
          function setNativeValueToCheckPauseState(uint256 _nativeValueToCheckPauseState) external onlyOwner {
              nativeValueToCheckPauseState = _nativeValueToCheckPauseState;
              configurationInitialized = true;
          }
          function getNativeValueToCheckPauseState() external view returns(uint256 _nativeValueToCheckPauseState) {
              if (!configurationInitialized) {
                  revert CreatorTokenTransferValidatorConfiguration__ConfigurationNotInitialized();
              }
              _nativeValueToCheckPauseState = nativeValueToCheckPauseState;
          }
      }// 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
      // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
      pragma solidity ^0.8.0;
      import "./IERC165.sol";
      /**
       * @dev Implementation of the {IERC165} interface.
       *
       * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
       * for the additional interface id that will be supported. For example:
       *
       * ```solidity
       * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
       *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
       * }
       * ```
       *
       * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
       */
      abstract contract ERC165 is IERC165 {
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(IERC165).interfaceId;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
      // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
      pragma solidity ^0.8.0;
      /**
       * @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.
       *
       * ```
       * 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 of the value in the `values` array, plus 1 because index 0
              // means a value is not in the set.
              mapping(bytes32 => uint256) _indexes;
          }
          /**
           * @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._indexes[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 read and store the value's index to prevent multiple reads from the same storage slot
              uint256 valueIndex = set._indexes[value];
              if (valueIndex != 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 toDeleteIndex = valueIndex - 1;
                  uint256 lastIndex = set._values.length - 1;
                  if (lastIndex != toDeleteIndex) {
                      bytes32 lastValue = set._values[lastIndex];
                      // Move the last value to the index where the value to delete is
                      set._values[toDeleteIndex] = lastValue;
                      // Update the index for the moved value
                      set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
                  }
                  // Delete the slot where the moved value was stored
                  set._values.pop();
                  // Delete the index for the deleted slot
                  delete set._indexes[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._indexes[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;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.24;
      contract Tstorish {
          // Declare a storage variable indicating if TSTORE support has been
          // activated post-deployment.
          bool private _tstoreSupport;
          /*
           * ------------------------------------------------------------------------+
           * 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 initial TSTORE support status.
          bool private immutable _tstoreInitialSupport;
          // Declare an immutable variable to store the tstore test contract address.
          address private immutable _tloadTestContract;
          // 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.
              bool tstoreInitialSupport = _testTload(tloadTestContract);
              // Store the result as an immutable.
              _tstoreInitialSupport = tstoreInitialSupport;
              // Set the address of the deployed TLOAD test contract as an immutable.
              _tloadTestContract = tloadTestContract;
          }
          /**
           * @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 {
              // Ensure this function is triggered from an externally-owned account.
              if (msg.sender != tx.origin) {
                  revert OnlyDirectCalls();
              }
              // Determine if TSTORE can potentially be activated.
              if (_tstoreInitialSupport || _tstoreSupport) {
                  revert TStoreAlreadyActivated();
              }
              // Determine if TSTORE can be activated and revert if not.
              if (!_testTload(_tloadTestContract)) {
                  revert TStoreNotSupported();
              }
              // Mark TSTORE as activated.
              _tstoreSupport = true;
          }
          /**
           * @dev Internal function to set a TSTORISH value.
           *
           * @param storageSlot The slot to write the TSTORISH value to.
           * @param value       The value to write to the given storage slot.
           */
          function _setTstorish(uint256 storageSlot, uint256 value) internal {
              if (_tstoreInitialSupport) {
                  assembly {
                      tstore(storageSlot, value)
                  }
              } else if (_tstoreSupport) {
                  assembly {
                      tstore(storageSlot, value)
                  }
              } else {
                  assembly {
                      sstore(storageSlot, value)
                  }
              }
          }
          /**
           * @dev Internal function to read a TSTORISH value.
           *
           * @param storageSlot The slot to read the TSTORISH value from.
           *
           * @return value The TSTORISH value at the given storage slot.
           */
          function _getTstorish(
              uint256 storageSlot
          ) internal view returns (uint256 value) {
              if (_tstoreInitialSupport) {
                  assembly {
                      value := tload(storageSlot)
                  }
              } else if (_tstoreSupport) {
                  assembly {
                      value := tload(storageSlot)
                  }
              } else {
                  assembly {
                      value := sload(storageSlot)
                  }
              }
          }
          /**
           * @dev Internal function to clear a TSTORISH value.
           *
           * @param storageSlot The slot to clear the TSTORISH value for.
           */
          function _clearTstorish(uint256 storageSlot) internal {
              if (_tstoreInitialSupport) {
                  assembly {
                      tstore(storageSlot, 0)
                  }
              } else if (_tstoreSupport) {
                  assembly {
                      tstore(storageSlot, 0)
                  }
              } else {
                  assembly {
                      sstore(storageSlot, 0)
                  }
              }
          }
          /**
           * @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 }("");
          }
      }// 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.7.0) (access/Ownable.sol)
      pragma solidity ^0.8.0;
      import "../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 {
          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 {
              require(owner() == _msgSender(), "Ownable: caller is not the owner");
          }
          /**
           * @dev Leaves the contract without owner. It will not be possible to call
           * `onlyOwner` functions anymore. Can only be called by the current owner.
           *
           * NOTE: Renouncing ownership will leave the contract without an owner,
           * thereby removing 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 {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              _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);
          }
      }
      // 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
      // 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 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 (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);
          }
      }
      // 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
      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.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
      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;
      /*
                                                           @@@@@@@@@@@@@@             
                                                          @@@@@@@@@@@@@@@@@@(         
                                                         @@@@@@@@@@@@@@@@@@@@@        
                                                        @@@@@@@@@@@@@@@@@@@@@@@@      
                                                                 #@@@@@@@@@@@@@@      
                                                                     @@@@@@@@@@@@     
                                  @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                                 @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                                @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                               @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                              @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                              @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                           @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                          @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                          @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                         @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                        @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                       @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                      @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                      @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
                     @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                   @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
                  @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                 .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
                 @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
                @@@@@@@@@@@@@@@                                                       
               @@@@@@@@@@@@@@@                                                        
              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
             @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
             @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
       
      * @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
      // 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.6.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.8.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 (last updated v4.7.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.8.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) {
              // 32 is the length in bytes of hash,
              // enforced by the type signature above
              return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
      32", hash));
          }
          /**
           * @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) {
              return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
      pragma solidity ^0.8.0;
      import "./math/Math.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 `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);
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.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) {
                      return prod0 / denominator;
                  }
                  // Make sure the result is less than 2^256. Also prevents denominator == 0.
                  require(denominator > prod1);
                  ///////////////////////////////////////////////
                  // 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 10, 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 * 8) < value ? 1 : 0);
              }
          }
      }
      

      File 4 of 4: NftLock
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)
      pragma solidity ^0.8.0;
      import "./IAccessControlEnumerableUpgradeable.sol";
      import "./AccessControlUpgradeable.sol";
      import "../utils/structs/EnumerableSetUpgradeable.sol";
      import "../proxy/utils/Initializable.sol";
      /**
       * @dev Extension of {AccessControl} that allows enumerating the members of each role.
       */
      abstract contract AccessControlEnumerableUpgradeable is Initializable, IAccessControlEnumerableUpgradeable, AccessControlUpgradeable {
          function __AccessControlEnumerable_init() internal onlyInitializing {
          }
          function __AccessControlEnumerable_init_unchained() internal onlyInitializing {
          }
          using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
          mapping(bytes32 => EnumerableSetUpgradeable.AddressSet) private _roleMembers;
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(IAccessControlEnumerableUpgradeable).interfaceId || super.supportsInterface(interfaceId);
          }
          /**
           * @dev Returns one of the accounts that have `role`. `index` must be a
           * value between 0 and {getRoleMemberCount}, non-inclusive.
           *
           * Role bearers are not sorted in any particular way, and their ordering may
           * change at any point.
           *
           * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
           * you perform all queries on the same block. See the following
           * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
           * for more information.
           */
          function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
              return _roleMembers[role].at(index);
          }
          /**
           * @dev Returns the number of accounts that have `role`. Can be used
           * together with {getRoleMember} to enumerate all bearers of a role.
           */
          function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
              return _roleMembers[role].length();
          }
          /**
           * @dev Overload {_grantRole} to track enumerable memberships
           */
          function _grantRole(bytes32 role, address account) internal virtual override {
              super._grantRole(role, account);
              _roleMembers[role].add(account);
          }
          /**
           * @dev Overload {_revokeRole} to track enumerable memberships
           */
          function _revokeRole(bytes32 role, address account) internal virtual override {
              super._revokeRole(role, account);
              _roleMembers[role].remove(account);
          }
          /**
           * @dev This empty reserved space is put in place to allow future versions to add new
           * variables without shifting down storage in the inheritance chain.
           * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
           */
          uint256[49] private __gap;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
      pragma solidity ^0.8.0;
      import "./IAccessControlUpgradeable.sol";
      import "../utils/ContextUpgradeable.sol";
      import "../utils/StringsUpgradeable.sol";
      import "../utils/introspection/ERC165Upgradeable.sol";
      import "../proxy/utils/Initializable.sol";
      /**
       * @dev Contract module that allows children to implement role-based access
       * control mechanisms. This is a lightweight version that doesn't allow enumerating role
       * members except through off-chain means by accessing the contract event logs. Some
       * applications may benefit from on-chain enumerability, for those cases see
       * {AccessControlEnumerable}.
       *
       * Roles are referred to by their `bytes32` identifier. These should be exposed
       * in the external API and be unique. The best way to achieve this is by
       * using `public constant` hash digests:
       *
       * ```
       * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
       * ```
       *
       * Roles can be used to represent a set of permissions. To restrict access to a
       * function call, use {hasRole}:
       *
       * ```
       * function foo() public {
       *     require(hasRole(MY_ROLE, msg.sender));
       *     ...
       * }
       * ```
       *
       * Roles can be granted and revoked dynamically via the {grantRole} and
       * {revokeRole} functions. Each role has an associated admin role, and only
       * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
       *
       * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
       * that only accounts with this role will be able to grant or revoke other
       * roles. More complex role relationships can be created by using
       * {_setRoleAdmin}.
       *
       * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
       * grant and revoke this role. Extra precautions should be taken to secure
       * accounts that have been granted it.
       */
      abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
          function __AccessControl_init() internal onlyInitializing {
          }
          function __AccessControl_init_unchained() internal onlyInitializing {
          }
          struct RoleData {
              mapping(address => bool) members;
              bytes32 adminRole;
          }
          mapping(bytes32 => RoleData) private _roles;
          bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
          /**
           * @dev Modifier that checks that an account has a specific role. Reverts
           * with a standardized message including the required role.
           *
           * The format of the revert reason is given by the following regular expression:
           *
           *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
           *
           * _Available since v4.1._
           */
          modifier onlyRole(bytes32 role) {
              _checkRole(role);
              _;
          }
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
          }
          /**
           * @dev Returns `true` if `account` has been granted `role`.
           */
          function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
              return _roles[role].members[account];
          }
          /**
           * @dev Revert with a standard message if `_msgSender()` is missing `role`.
           * Overriding this function changes the behavior of the {onlyRole} modifier.
           *
           * Format of the revert message is described in {_checkRole}.
           *
           * _Available since v4.6._
           */
          function _checkRole(bytes32 role) internal view virtual {
              _checkRole(role, _msgSender());
          }
          /**
           * @dev Revert with a standard message if `account` is missing `role`.
           *
           * The format of the revert reason is given by the following regular expression:
           *
           *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
           */
          function _checkRole(bytes32 role, address account) internal view virtual {
              if (!hasRole(role, account)) {
                  revert(
                      string(
                          abi.encodePacked(
                              "AccessControl: account ",
                              StringsUpgradeable.toHexString(account),
                              " is missing role ",
                              StringsUpgradeable.toHexString(uint256(role), 32)
                          )
                      )
                  );
              }
          }
          /**
           * @dev Returns the admin role that controls `role`. See {grantRole} and
           * {revokeRole}.
           *
           * To change a role's admin, use {_setRoleAdmin}.
           */
          function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
              return _roles[role].adminRole;
          }
          /**
           * @dev Grants `role` to `account`.
           *
           * If `account` had not been already granted `role`, emits a {RoleGranted}
           * event.
           *
           * Requirements:
           *
           * - the caller must have ``role``'s admin role.
           *
           * May emit a {RoleGranted} event.
           */
          function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
              _grantRole(role, account);
          }
          /**
           * @dev Revokes `role` from `account`.
           *
           * If `account` had been granted `role`, emits a {RoleRevoked} event.
           *
           * Requirements:
           *
           * - the caller must have ``role``'s admin role.
           *
           * May emit a {RoleRevoked} event.
           */
          function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
              _revokeRole(role, account);
          }
          /**
           * @dev Revokes `role` from the calling account.
           *
           * Roles are often managed via {grantRole} and {revokeRole}: this function's
           * purpose is to provide a mechanism for accounts to lose their privileges
           * if they are compromised (such as when a trusted device is misplaced).
           *
           * If the calling account had been revoked `role`, emits a {RoleRevoked}
           * event.
           *
           * Requirements:
           *
           * - the caller must be `account`.
           *
           * May emit a {RoleRevoked} event.
           */
          function renounceRole(bytes32 role, address account) public virtual override {
              require(account == _msgSender(), "AccessControl: can only renounce roles for self");
              _revokeRole(role, account);
          }
          /**
           * @dev Grants `role` to `account`.
           *
           * If `account` had not been already granted `role`, emits a {RoleGranted}
           * event. Note that unlike {grantRole}, this function doesn't perform any
           * checks on the calling account.
           *
           * May emit a {RoleGranted} event.
           *
           * [WARNING]
           * ====
           * This function should only be called from the constructor when setting
           * up the initial roles for the system.
           *
           * Using this function in any other way is effectively circumventing the admin
           * system imposed by {AccessControl}.
           * ====
           *
           * NOTE: This function is deprecated in favor of {_grantRole}.
           */
          function _setupRole(bytes32 role, address account) internal virtual {
              _grantRole(role, account);
          }
          /**
           * @dev Sets `adminRole` as ``role``'s admin role.
           *
           * Emits a {RoleAdminChanged} event.
           */
          function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
              bytes32 previousAdminRole = getRoleAdmin(role);
              _roles[role].adminRole = adminRole;
              emit RoleAdminChanged(role, previousAdminRole, adminRole);
          }
          /**
           * @dev Grants `role` to `account`.
           *
           * Internal function without access restriction.
           *
           * May emit a {RoleGranted} event.
           */
          function _grantRole(bytes32 role, address account) internal virtual {
              if (!hasRole(role, account)) {
                  _roles[role].members[account] = true;
                  emit RoleGranted(role, account, _msgSender());
              }
          }
          /**
           * @dev Revokes `role` from `account`.
           *
           * Internal function without access restriction.
           *
           * May emit a {RoleRevoked} event.
           */
          function _revokeRole(bytes32 role, address account) internal virtual {
              if (hasRole(role, account)) {
                  _roles[role].members[account] = false;
                  emit RoleRevoked(role, account, _msgSender());
              }
          }
          /**
           * @dev This empty reserved space is put in place to allow future versions to add new
           * variables without shifting down storage in the inheritance chain.
           * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
           */
          uint256[49] private __gap;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)
      pragma solidity ^0.8.0;
      import "./IAccessControlUpgradeable.sol";
      /**
       * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.
       */
      interface IAccessControlEnumerableUpgradeable is IAccessControlUpgradeable {
          /**
           * @dev Returns one of the accounts that have `role`. `index` must be a
           * value between 0 and {getRoleMemberCount}, non-inclusive.
           *
           * Role bearers are not sorted in any particular way, and their ordering may
           * change at any point.
           *
           * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure
           * you perform all queries on the same block. See the following
           * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]
           * for more information.
           */
          function getRoleMember(bytes32 role, uint256 index) external view returns (address);
          /**
           * @dev Returns the number of accounts that have `role`. Can be used
           * together with {getRoleMember} to enumerate all bearers of a role.
           */
          function getRoleMemberCount(bytes32 role) external view returns (uint256);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev External interface of AccessControl declared to support ERC165 detection.
       */
      interface IAccessControlUpgradeable {
          /**
           * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
           *
           * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
           * {RoleAdminChanged} not being emitted signaling this.
           *
           * _Available since v3.1._
           */
          event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
          /**
           * @dev Emitted when `account` is granted `role`.
           *
           * `sender` is the account that originated the contract call, an admin role
           * bearer except when using {AccessControl-_setupRole}.
           */
          event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
          /**
           * @dev Emitted when `account` is revoked `role`.
           *
           * `sender` is the account that originated the contract call:
           *   - if using `revokeRole`, it is the admin role bearer
           *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
           */
          event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
          /**
           * @dev Returns `true` if `account` has been granted `role`.
           */
          function hasRole(bytes32 role, address account) external view returns (bool);
          /**
           * @dev Returns the admin role that controls `role`. See {grantRole} and
           * {revokeRole}.
           *
           * To change a role's admin, use {AccessControl-_setRoleAdmin}.
           */
          function getRoleAdmin(bytes32 role) external view returns (bytes32);
          /**
           * @dev Grants `role` to `account`.
           *
           * If `account` had not been already granted `role`, emits a {RoleGranted}
           * event.
           *
           * Requirements:
           *
           * - the caller must have ``role``'s admin role.
           */
          function grantRole(bytes32 role, address account) external;
          /**
           * @dev Revokes `role` from `account`.
           *
           * If `account` had been granted `role`, emits a {RoleRevoked} event.
           *
           * Requirements:
           *
           * - the caller must have ``role``'s admin role.
           */
          function revokeRole(bytes32 role, address account) external;
          /**
           * @dev Revokes `role` from the calling account.
           *
           * Roles are often managed via {grantRole} and {revokeRole}: this function's
           * purpose is to provide a mechanism for accounts to lose their privileges
           * if they are compromised (such as when a trusted device is misplaced).
           *
           * If the calling account had been granted `role`, emits a {RoleRevoked}
           * event.
           *
           * Requirements:
           *
           * - the caller must be `account`.
           */
          function renounceRole(bytes32 role, address account) external;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (proxy/utils/Initializable.sol)
      pragma solidity ^0.8.2;
      import "../../utils/AddressUpgradeable.sol";
      /**
       * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
       * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
       * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
       * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
       *
       * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
       * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
       * case an upgrade adds a module that needs to be initialized.
       *
       * For example:
       *
       * [.hljs-theme-light.nopadding]
       * ```
       * contract MyToken is ERC20Upgradeable {
       *     function initialize() initializer public {
       *         __ERC20_init("MyToken", "MTK");
       *     }
       * }
       * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
       *     function initializeV2() reinitializer(2) public {
       *         __ERC20Permit_init("MyToken");
       *     }
       * }
       * ```
       *
       * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
       * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
       *
       * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
       * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
       *
       * [CAUTION]
       * ====
       * Avoid leaving a contract uninitialized.
       *
       * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
       * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
       * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
       *
       * [.hljs-theme-light.nopadding]
       * ```
       * /// @custom:oz-upgrades-unsafe-allow constructor
       * constructor() {
       *     _disableInitializers();
       * }
       * ```
       * ====
       */
      abstract contract Initializable {
          /**
           * @dev Indicates that the contract has been initialized.
           * @custom:oz-retyped-from bool
           */
          uint8 private _initialized;
          /**
           * @dev Indicates that the contract is in the process of being initialized.
           */
          bool private _initializing;
          /**
           * @dev Triggered when the contract has been initialized or reinitialized.
           */
          event Initialized(uint8 version);
          /**
           * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
           * `onlyInitializing` functions can be used to initialize parent contracts.
           *
           * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
           * constructor.
           *
           * Emits an {Initialized} event.
           */
          modifier initializer() {
              bool isTopLevelCall = !_initializing;
              require(
                  (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                  "Initializable: contract is already initialized"
              );
              _initialized = 1;
              if (isTopLevelCall) {
                  _initializing = true;
              }
              _;
              if (isTopLevelCall) {
                  _initializing = false;
                  emit Initialized(1);
              }
          }
          /**
           * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
           * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
           * used to initialize parent contracts.
           *
           * A reinitializer may be used after the original initialization step. This is essential to configure modules that
           * are added through upgrades and that require initialization.
           *
           * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
           * cannot be nested. If one is invoked in the context of another, execution will revert.
           *
           * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
           * a contract, executing them in the right order is up to the developer or operator.
           *
           * WARNING: setting the version to 255 will prevent any future reinitialization.
           *
           * Emits an {Initialized} event.
           */
          modifier reinitializer(uint8 version) {
              require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
              _initialized = version;
              _initializing = true;
              _;
              _initializing = false;
              emit Initialized(version);
          }
          /**
           * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
           * {initializer} and {reinitializer} modifiers, directly or indirectly.
           */
          modifier onlyInitializing() {
              require(_initializing, "Initializable: contract is not initializing");
              _;
          }
          /**
           * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
           * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
           * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
           * through proxies.
           *
           * Emits an {Initialized} event the first time it is successfully executed.
           */
          function _disableInitializers() internal virtual {
              require(!_initializing, "Initializable: contract is initializing");
              if (_initialized < type(uint8).max) {
                  _initialized = type(uint8).max;
                  emit Initialized(type(uint8).max);
              }
          }
          /**
           * @dev Internal function that returns the initialized version. Returns `_initialized`
           */
          function _getInitializedVersion() internal view returns (uint8) {
              return _initialized;
          }
          /**
           * @dev Internal function that returns the initialized version. Returns `_initializing`
           */
          function _isInitializing() internal view returns (bool) {
              return _initializing;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
      pragma solidity ^0.8.0;
      import "../IERC721Upgradeable.sol";
      /**
       * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
       * @dev See https://eips.ethereum.org/EIPS/eip-721
       */
      interface IERC721EnumerableUpgradeable is IERC721Upgradeable {
          /**
           * @dev Returns the total amount of tokens stored by the contract.
           */
          function totalSupply() external view returns (uint256);
          /**
           * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
           * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
           */
          function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
          /**
           * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
           * Use along with {totalSupply} to enumerate all tokens.
           */
          function tokenByIndex(uint256 index) external view returns (uint256);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
      pragma solidity ^0.8.0;
      /**
       * @title ERC721 token receiver interface
       * @dev Interface for any contract that wants to support safeTransfers
       * from ERC721 asset contracts.
       */
      interface IERC721ReceiverUpgradeable {
          /**
           * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
           * by `operator` from `from`, this function is called.
           *
           * It must return its Solidity selector to confirm the token transfer.
           * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
           *
           * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
           */
          function onERC721Received(
              address operator,
              address from,
              uint256 tokenId,
              bytes calldata data
          ) external returns (bytes4);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
      pragma solidity ^0.8.0;
      import "../../utils/introspection/IERC165Upgradeable.sol";
      /**
       * @dev Required interface of an ERC721 compliant contract.
       */
      interface IERC721Upgradeable is IERC165Upgradeable {
          /**
           * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
           */
          event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
          /**
           * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
           */
          event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
          /**
           * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
           */
          event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
          /**
           * @dev Returns the number of tokens in ``owner``'s account.
           */
          function balanceOf(address owner) external view returns (uint256 balance);
          /**
           * @dev Returns the owner of the `tokenId` token.
           *
           * Requirements:
           *
           * - `tokenId` must exist.
           */
          function ownerOf(uint256 tokenId) external view returns (address owner);
          /**
           * @dev Safely transfers `tokenId` token from `from` to `to`.
           *
           * Requirements:
           *
           * - `from` cannot be the zero address.
           * - `to` cannot be the zero address.
           * - `tokenId` token must exist and be owned by `from`.
           * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
           * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
           *
           * Emits a {Transfer} event.
           */
          function safeTransferFrom(
              address from,
              address to,
              uint256 tokenId,
              bytes calldata data
          ) external;
          /**
           * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
           * are aware of the ERC721 protocol to prevent tokens from being forever locked.
           *
           * Requirements:
           *
           * - `from` cannot be the zero address.
           * - `to` cannot be the zero address.
           * - `tokenId` token must exist and be owned by `from`.
           * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
           * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
           *
           * Emits a {Transfer} event.
           */
          function safeTransferFrom(
              address from,
              address to,
              uint256 tokenId
          ) external;
          /**
           * @dev Transfers `tokenId` token from `from` to `to`.
           *
           * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
           * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
           * understand this adds an external call which potentially creates a reentrancy vulnerability.
           *
           * Requirements:
           *
           * - `from` cannot be the zero address.
           * - `to` cannot be the zero address.
           * - `tokenId` token must be owned by `from`.
           * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(
              address from,
              address to,
              uint256 tokenId
          ) external;
          /**
           * @dev Gives permission to `to` to transfer `tokenId` token to another account.
           * The approval is cleared when the token is transferred.
           *
           * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
           *
           * Requirements:
           *
           * - The caller must own the token or be an approved operator.
           * - `tokenId` must exist.
           *
           * Emits an {Approval} event.
           */
          function approve(address to, uint256 tokenId) external;
          /**
           * @dev Approve or remove `operator` as an operator for the caller.
           * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
           *
           * Requirements:
           *
           * - The `operator` cannot be the caller.
           *
           * Emits an {ApprovalForAll} event.
           */
          function setApprovalForAll(address operator, bool _approved) external;
          /**
           * @dev Returns the account approved for `tokenId` token.
           *
           * Requirements:
           *
           * - `tokenId` must exist.
           */
          function getApproved(uint256 tokenId) external view returns (address operator);
          /**
           * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
           *
           * See {setApprovalForAll}
           */
          function isApprovedForAll(address owner, address operator) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
      pragma solidity ^0.8.1;
      /**
       * @dev Collection of functions related to the address type
       */
      library AddressUpgradeable {
          /**
           * @dev Returns true if `account` is a contract.
           *
           * [IMPORTANT]
           * ====
           * It is unsafe to assume that an address for which this function returns
           * false is an externally-owned account (EOA) and not a contract.
           *
           * Among others, `isContract` will return false for the following
           * types of addresses:
           *
           *  - an externally-owned account
           *  - a contract in construction
           *  - an address where a contract will be created
           *  - an address where a contract lived, but was destroyed
           * ====
           *
           * [IMPORTANT]
           * ====
           * You shouldn't rely on `isContract` to protect against flash loan attacks!
           *
           * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
           * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
           * constructor.
           * ====
           */
          function isContract(address account) internal view returns (bool) {
              // This method relies on extcodesize/address.code.length, which returns 0
              // for contracts in construction, since the code is only stored at the end
              // of the constructor execution.
              return account.code.length > 0;
          }
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
              (bool success, ) = recipient.call{value: amount}("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain `call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, "Address: low-level call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value
          ) internal returns (bytes memory) {
              return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
          }
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value,
              string memory errorMessage
          ) internal returns (bytes memory) {
              require(address(this).balance >= value, "Address: insufficient balance for call");
              (bool success, bytes memory returndata) = target.call{value: value}(data);
              return verifyCallResultFromTarget(target, success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
              return functionStaticCall(target, data, "Address: low-level static call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal view returns (bytes memory) {
              (bool success, bytes memory returndata) = target.staticcall(data);
              return verifyCallResultFromTarget(target, success, returndata, errorMessage);
          }
          /**
           * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
           * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
           *
           * _Available since v4.8._
           */
          function verifyCallResultFromTarget(
              address target,
              bool success,
              bytes memory returndata,
              string memory errorMessage
          ) internal view returns (bytes memory) {
              if (success) {
                  if (returndata.length == 0) {
                      // only check isContract if the call was successful and the return data is empty
                      // otherwise we already know that it was a contract
                      require(isContract(target), "Address: call to non-contract");
                  }
                  return returndata;
              } else {
                  _revert(returndata, errorMessage);
              }
          }
          /**
           * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
           * revert reason or using the provided one.
           *
           * _Available since v4.3._
           */
          function verifyCallResult(
              bool success,
              bytes memory returndata,
              string memory errorMessage
          ) internal pure returns (bytes memory) {
              if (success) {
                  return returndata;
              } else {
                  _revert(returndata, errorMessage);
              }
          }
          function _revert(bytes memory returndata, string memory errorMessage) private pure {
              // Look for revert reason and bubble it up if present
              if (returndata.length > 0) {
                  // The easiest way to bubble the revert reason is using memory via assembly
                  /// @solidity memory-safe-assembly
                  assembly {
                      let returndata_size := mload(returndata)
                      revert(add(32, returndata), returndata_size)
                  }
              } else {
                  revert(errorMessage);
              }
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
      pragma solidity ^0.8.0;
      import "../proxy/utils/Initializable.sol";
      /**
       * @dev Provides information about the current execution context, including the
       * sender of the transaction and its data. While these are generally available
       * via msg.sender and msg.data, they should not be accessed in such a direct
       * manner, since when dealing with meta-transactions the account sending and
       * paying for execution may not be the actual sender (as far as an application
       * is concerned).
       *
       * This contract is only required for intermediate, library-like contracts.
       */
      abstract contract ContextUpgradeable is Initializable {
          function __Context_init() internal onlyInitializing {
          }
          function __Context_init_unchained() internal onlyInitializing {
          }
          function _msgSender() internal view virtual returns (address) {
              return msg.sender;
          }
          function _msgData() internal view virtual returns (bytes calldata) {
              return msg.data;
          }
          /**
           * @dev This empty reserved space is put in place to allow future versions to add new
           * variables without shifting down storage in the inheritance chain.
           * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
           */
          uint256[50] private __gap;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol)
      pragma solidity ^0.8.0;
      import "./IERC165Upgradeable.sol";
      /**
       * @dev Library used to query support of an interface declared via {IERC165}.
       *
       * Note that these functions return the actual result of the query: they do not
       * `revert` if an interface is not supported. It is up to the caller to decide
       * what to do in these cases.
       */
      library ERC165CheckerUpgradeable {
          // As per the EIP-165 spec, no interface should ever match 0xffffffff
          bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
          /**
           * @dev Returns true if `account` supports the {IERC165} interface.
           */
          function supportsERC165(address account) internal view returns (bool) {
              // Any contract that implements ERC165 must explicitly indicate support of
              // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
              return
                  supportsERC165InterfaceUnchecked(account, type(IERC165Upgradeable).interfaceId) &&
                  !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
          }
          /**
           * @dev Returns true if `account` supports the interface defined by
           * `interfaceId`. Support for {IERC165} itself is queried automatically.
           *
           * See {IERC165-supportsInterface}.
           */
          function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
              // query support of both ERC165 as per the spec and support of _interfaceId
              return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
          }
          /**
           * @dev Returns a boolean array where each value corresponds to the
           * interfaces passed in and whether they're supported or not. This allows
           * you to batch check interfaces for a contract where your expectation
           * is that some interfaces may not be supported.
           *
           * See {IERC165-supportsInterface}.
           *
           * _Available since v3.4._
           */
          function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
              internal
              view
              returns (bool[] memory)
          {
              // an array of booleans corresponding to interfaceIds and whether they're supported or not
              bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
              // query support of ERC165 itself
              if (supportsERC165(account)) {
                  // query support of each interface in interfaceIds
                  for (uint256 i = 0; i < interfaceIds.length; i++) {
                      interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
                  }
              }
              return interfaceIdsSupported;
          }
          /**
           * @dev Returns true if `account` supports all the interfaces defined in
           * `interfaceIds`. Support for {IERC165} itself is queried automatically.
           *
           * Batch-querying can lead to gas savings by skipping repeated checks for
           * {IERC165} support.
           *
           * See {IERC165-supportsInterface}.
           */
          function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
              // query support of ERC165 itself
              if (!supportsERC165(account)) {
                  return false;
              }
              // query support of each interface in interfaceIds
              for (uint256 i = 0; i < interfaceIds.length; i++) {
                  if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                      return false;
                  }
              }
              // all interfaces supported
              return true;
          }
          /**
           * @notice Query if a contract implements an interface, does not check ERC165 support
           * @param account The address of the contract to query for support of an interface
           * @param interfaceId The interface identifier, as specified in ERC-165
           * @return true if the contract at account indicates support of the interface with
           * identifier interfaceId, false otherwise
           * @dev Assumes that account contains a contract that supports ERC165, otherwise
           * the behavior of this method is undefined. This precondition can be checked
           * with {supportsERC165}.
           * Interface identification is specified in ERC-165.
           */
          function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
              // prepare call
              bytes memory encodedParams = abi.encodeWithSelector(IERC165Upgradeable.supportsInterface.selector, interfaceId);
              // perform static call
              bool success;
              uint256 returnSize;
              uint256 returnValue;
              assembly {
                  success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
                  returnSize := returndatasize()
                  returnValue := mload(0x00)
              }
              return success && returnSize >= 0x20 && returnValue > 0;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
      pragma solidity ^0.8.0;
      import "./IERC165Upgradeable.sol";
      import "../../proxy/utils/Initializable.sol";
      /**
       * @dev Implementation of the {IERC165} interface.
       *
       * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
       * for the additional interface id that will be supported. For example:
       *
       * ```solidity
       * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
       *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
       * }
       * ```
       *
       * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
       */
      abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
          function __ERC165_init() internal onlyInitializing {
          }
          function __ERC165_init_unchained() internal onlyInitializing {
          }
          /**
           * @dev See {IERC165-supportsInterface}.
           */
          function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
              return interfaceId == type(IERC165Upgradeable).interfaceId;
          }
          /**
           * @dev This empty reserved space is put in place to allow future versions to add new
           * variables without shifting down storage in the inheritance chain.
           * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
           */
          uint256[50] private __gap;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Interface of the ERC165 standard, as defined in the
       * https://eips.ethereum.org/EIPS/eip-165[EIP].
       *
       * Implementers can declare support of contract interfaces, which can then be
       * queried by others ({ERC165Checker}).
       *
       * For an implementation, see {ERC165}.
       */
      interface IERC165Upgradeable {
          /**
           * @dev Returns true if this contract implements the interface defined by
           * `interfaceId`. See the corresponding
           * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
           * to learn more about how these ids are created.
           *
           * This function call must use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Standard math utilities missing in the Solidity language.
       */
      library MathUpgradeable {
          enum Rounding {
              Down, // Toward negative infinity
              Up, // Toward infinity
              Zero // Toward zero
          }
          /**
           * @dev Returns the largest of two numbers.
           */
          function max(uint256 a, uint256 b) internal pure returns (uint256) {
              return a > b ? a : b;
          }
          /**
           * @dev Returns the smallest of two numbers.
           */
          function min(uint256 a, uint256 b) internal pure returns (uint256) {
              return a < b ? a : b;
          }
          /**
           * @dev Returns the average of two numbers. The result is rounded towards
           * zero.
           */
          function average(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b) / 2 can overflow.
              return (a & b) + (a ^ b) / 2;
          }
          /**
           * @dev Returns the ceiling of the division of two numbers.
           *
           * This differs from standard division with `/` in that it rounds up instead
           * of rounding down.
           */
          function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b - 1) / b can overflow on addition, so we distribute.
              return a == 0 ? 0 : (a - 1) / b + 1;
          }
          /**
           * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
           * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
           * with further edits by Uniswap Labs also under MIT license.
           */
          function mulDiv(
              uint256 x,
              uint256 y,
              uint256 denominator
          ) internal pure returns (uint256 result) {
              unchecked {
                  // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                  // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                  // variables such that product = prod1 * 2^256 + prod0.
                  uint256 prod0; // Least significant 256 bits of the product
                  uint256 prod1; // Most significant 256 bits of the product
                  assembly {
                      let mm := mulmod(x, y, not(0))
                      prod0 := mul(x, y)
                      prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                  }
                  // Handle non-overflow cases, 256 by 256 division.
                  if (prod1 == 0) {
                      return prod0 / denominator;
                  }
                  // Make sure the result is less than 2^256. Also prevents denominator == 0.
                  require(denominator > prod1);
                  ///////////////////////////////////////////////
                  // 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 10, 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 * 8) < value ? 1 : 0);
              }
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
      pragma solidity ^0.8.0;
      import "./math/MathUpgradeable.sol";
      /**
       * @dev String operations.
       */
      library StringsUpgradeable {
          bytes16 private constant _SYMBOLS = "0123456789abcdef";
          uint8 private constant _ADDRESS_LENGTH = 20;
          /**
           * @dev Converts a `uint256` to its ASCII `string` decimal representation.
           */
          function toString(uint256 value) internal pure returns (string memory) {
              unchecked {
                  uint256 length = MathUpgradeable.log10(value) + 1;
                  string memory buffer = new string(length);
                  uint256 ptr;
                  /// @solidity memory-safe-assembly
                  assembly {
                      ptr := add(buffer, add(32, length))
                  }
                  while (true) {
                      ptr--;
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                      }
                      value /= 10;
                      if (value == 0) break;
                  }
                  return buffer;
              }
          }
          /**
           * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
           */
          function toHexString(uint256 value) internal pure returns (string memory) {
              unchecked {
                  return toHexString(value, MathUpgradeable.log256(value) + 1);
              }
          }
          /**
           * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
           */
          function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
              bytes memory buffer = new bytes(2 * length + 2);
              buffer[0] = "0";
              buffer[1] = "x";
              for (uint256 i = 2 * length + 1; i > 1; --i) {
                  buffer[i] = _SYMBOLS[value & 0xf];
                  value >>= 4;
              }
              require(value == 0, "Strings: hex length insufficient");
              return string(buffer);
          }
          /**
           * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
           */
          function toHexString(address addr) internal pure returns (string memory) {
              return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
      // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
      pragma solidity ^0.8.0;
      /**
       * @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.
       *
       * ```
       * 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 EnumerableSetUpgradeable {
          // 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 of the value in the `values` array, plus 1 because index 0
              // means a value is not in the set.
              mapping(bytes32 => uint256) _indexes;
          }
          /**
           * @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._indexes[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 read and store the value's index to prevent multiple reads from the same storage slot
              uint256 valueIndex = set._indexes[value];
              if (valueIndex != 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 toDeleteIndex = valueIndex - 1;
                  uint256 lastIndex = set._values.length - 1;
                  if (lastIndex != toDeleteIndex) {
                      bytes32 lastValue = set._values[lastIndex];
                      // Move the last value to the index where the value to delete is
                      set._values[toDeleteIndex] = lastValue;
                      // Update the index for the moved value
                      set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
                  }
                  // Delete the slot where the moved value was stored
                  set._values.pop();
                  // Delete the index for the deleted slot
                  delete set._indexes[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._indexes[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;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.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/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: UNLICENSED
      pragma solidity ^0.8.17;
      import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol';
      interface ILockNft is IERC721EnumerableUpgradeable {
          function lockerTransferFrom(address from, address to, uint256 tokenId) external;
      }
      // SPDX-License-Identifier: UNLICENSED
      pragma solidity ^0.8.17;
      import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
      import '@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol';
      import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol';
      import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol';
      import '@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol';
      import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol';
      import '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol';
      import './interface/ILockNft.sol';
      contract NftLock is ContextUpgradeable, AccessControlEnumerableUpgradeable, IERC721ReceiverUpgradeable {
          using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
          bytes32 public constant LOCKER_ROLE = keccak256('LOCKER_ROLE');
          mapping(address => mapping(uint256 => address)) public tokenOwners;
          event Deposit(address indexed token, address indexed user, uint256 id);
          event Withdraw(address indexed token, address indexed user, uint256 id);
          /// @custom:oz-upgrades-unsafe-allow constructor
          constructor() {
              _disableInitializers();
          }
          function initialize() public initializer {
              __AccessControlEnumerable_init();
              _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
              _setupRole(LOCKER_ROLE, _msgSender());
          }
          function getTokenOwner(address _tokenAddress, uint256 _id) public view returns (address) {
              return tokenOwners[_tokenAddress][_id];
          }
          function deposit(address user, ILockNft _tokenAddress, uint256 _id) public onlyRole(LOCKER_ROLE) {
              _tokenAddress.lockerTransferFrom(user, address(this), _id);
              tokenOwners[address(_tokenAddress)][_id] = user;
              emit Deposit(address(_tokenAddress), user, _id);
          }
          function withdraw(IERC721Upgradeable _tokenAddress, uint256 _id) public onlyRole(LOCKER_ROLE) {
              require(tokenOwners[address(_tokenAddress)][_id] != address(0), 'invalid token owner');
              _tokenAddress.safeTransferFrom(address(this), tokenOwners[address(_tokenAddress)][_id], _id);
              tokenOwners[address(_tokenAddress)][_id] = address(0);
              emit Withdraw(address(_tokenAddress), tokenOwners[address(_tokenAddress)][_id], _id);
          }
          function withdrawByAdmin(IERC721Upgradeable _tokenAddress, uint256 _id) public onlyRole(LOCKER_ROLE) {
              require(tokenOwners[address(_tokenAddress)][_id] != address(0), 'invalid token owner');
              _tokenAddress.safeTransferFrom(address(this), _msgSender(), _id);
              tokenOwners[address(_tokenAddress)][_id] = address(0);
              emit Withdraw(address(_tokenAddress), _msgSender(), _id);
          }
          function setTokenOwnerByAdmin(
              IERC721Upgradeable _tokenAddress,
              uint256 _id,
              address newOwner
          ) public onlyRole(DEFAULT_ADMIN_ROLE) {
              tokenOwners[address(_tokenAddress)][_id] = newOwner;
          }
          function onERC721Received(
              address,
              address from,
              uint256 tokenId,
              bytes calldata
          ) external override returns (bytes4) {
              address tokenAddress = msg.sender;
              if (tokenOwners[tokenAddress][tokenId] == from) {
                  return this.onERC721Received.selector;
              } else {
                  require(tokenOwners[tokenAddress][tokenId] == address(0), 'Token already owned');
                  tokenOwners[tokenAddress][tokenId] = from;
                  emit Deposit(tokenAddress, from, tokenId);
                  return this.onERC721Received.selector;
              }
          }
      }