ETH Price: $1,974.76 (+0.04%)

Transaction Decoder

Block:
24501425 at Feb-20-2026 11:42:47 PM +UTC
Transaction Fee:
0.000003497989724677 ETH $0.006908
Gas Used:
69,769 Gas / 0.050136733 Gwei

Emitted Events:

3528 0xa16cd7625fdf6820b9eff7827553e1ed38c84143.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000009c5d3a235c3dbb7dbce591f7556bdac12e88c2d4, 0x00000000000000000000000095e55841423a525645a85d867e41dcda3b8d3402, 0x000000000000000000000000000000000000000000000000000000000000079b )

Account State Difference:

  Address   Before After State Difference Code
0x9c5D3a23...12e88C2D4
0.000021754380171593 Eth
Nonce: 209
0.000018256390446916 Eth
Nonce: 210
0.000003497989724677
0xa16CD762...d38c84143
(BuilderNet)
127.211533548044294151 Eth127.211533549105899255 Eth0.000000001061605104

Execution Trace

0xa16cd7625fdf6820b9eff7827553e1ed38c84143.42842e0e( )
  • ERC721SeaDropCloneable.safeTransferFrom( from=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, to=0x95e55841423a525645A85D867E41DcdA3b8d3402, tokenId=1947 )
    • CreatorTokenTransferValidator.validateTransfer( caller=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, from=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, to=0x95e55841423a525645A85D867E41DcdA3b8d3402, tokenId=1947 )
      • CreatorTokenTransferValidator.validateTransferDelegateCall( authorizerCheckType=1, collection=0xa16CD7625fDF6820B9EfF7827553e1Ed38c84143, caller=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, from=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, to=0x95e55841423a525645A85D867E41DcdA3b8d3402, tokenId=1947, amount=0 ) => ( errorSelector=System.Byte[], tokenType=0 )
        • RulesetWhitelist.validateTransfer( authorizerCheckType=1, collection=0xa16CD7625fDF6820B9EfF7827553e1Ed38c84143, caller=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, from=0x9c5D3a235c3DBB7dBce591f7556bdAc12e88C2D4, to=0x95e55841423a525645A85D867E41DcdA3b8d3402, tokenId=1947, amount=0 ) => ( System.Byte[] )
          File 1 of 3: ERC721SeaDropCloneable
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import {
              ERC721ContractMetadataCloneable,
              ISeaDropTokenContractMetadata
          } from "./ERC721ContractMetadataCloneable.sol";
          import {
              INonFungibleSeaDropToken
          } from "../interfaces/INonFungibleSeaDropToken.sol";
          import { ISeaDrop } from "../interfaces/ISeaDrop.sol";
          import {
              AllowListData,
              PublicDrop,
              TokenGatedDropStage,
              SignedMintValidationParams
          } from "../lib/SeaDropStructs.sol";
          import {
              ERC721SeaDropStructsErrorsAndEvents
          } from "../lib/ERC721SeaDropStructsErrorsAndEvents.sol";
          import { ERC721ACloneable } from "./ERC721ACloneable.sol";
          import {
              ReentrancyGuardUpgradeable
          } from "openzeppelin-contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import {
              IERC165
          } from "openzeppelin-contracts/utils/introspection/IERC165.sol";
          /**
           * @title  ERC721SeaDrop
           * @author James Wenzel (emo.eth)
           * @author Ryan Ghods (ralxz.eth)
           * @author Stephan Min (stephanm.eth)
           * @author Michael Cohen (notmichael.eth)
           * @custom:contributor Limit Break (@limitbreak)
           * @notice ERC721SeaDrop is a token contract that contains methods
           *         to properly interact with SeaDrop.
           *         Implements Limit Break's Creator Token Standards transfer
           *         validation for royalty enforcement.
           */
          contract ERC721SeaDropCloneable is
              ERC721ContractMetadataCloneable,
              INonFungibleSeaDropToken,
              ERC721SeaDropStructsErrorsAndEvents,
              ReentrancyGuardUpgradeable
          {
              /// @notice Track the allowed SeaDrop addresses.
              mapping(address => bool) internal _allowedSeaDrop;
              /// @notice Track the enumerated allowed SeaDrop addresses.
              address[] internal _enumeratedAllowedSeaDrop;
              /**
               * @dev Reverts if not an allowed SeaDrop contract.
               *      This function is inlined instead of being a modifier
               *      to save contract space from being inlined N times.
               *
               * @param seaDrop The SeaDrop address to check if allowed.
               */
              function _onlyAllowedSeaDrop(address seaDrop) internal view {
                  if (_allowedSeaDrop[seaDrop] != true) {
                      revert OnlyAllowedSeaDrop();
                  }
              }
              /**
               * @notice Deploy the token contract with its name, symbol,
               *         and allowed SeaDrop addresses.
               */
              function initialize(
                  string calldata __name,
                  string calldata __symbol,
                  address[] calldata allowedSeaDrop,
                  address initialOwner
              ) public initializer {
                  __ERC721ACloneable__init(__name, __symbol);
                  __ReentrancyGuard_init();
                  _updateAllowedSeaDrop(allowedSeaDrop);
                  _transferOwnership(initialOwner);
                  emit SeaDropTokenDeployed();
              }
              /**
               * @notice Update the allowed SeaDrop contracts.
               *         Only the owner can use this function.
               *
               * @param allowedSeaDrop The allowed SeaDrop addresses.
               */
              function updateAllowedSeaDrop(address[] calldata allowedSeaDrop)
                  external
                  virtual
                  override
                  onlyOwner
              {
                  _updateAllowedSeaDrop(allowedSeaDrop);
              }
              /**
               * @notice Internal function to update the allowed SeaDrop contracts.
               *
               * @param allowedSeaDrop The allowed SeaDrop addresses.
               */
              function _updateAllowedSeaDrop(address[] calldata allowedSeaDrop) internal {
                  // Put the length on the stack for more efficient access.
                  uint256 enumeratedAllowedSeaDropLength = _enumeratedAllowedSeaDrop
                      .length;
                  uint256 allowedSeaDropLength = allowedSeaDrop.length;
                  // Reset the old mapping.
                  for (uint256 i = 0; i < enumeratedAllowedSeaDropLength; ) {
                      _allowedSeaDrop[_enumeratedAllowedSeaDrop[i]] = false;
                      unchecked {
                          ++i;
                      }
                  }
                  // Set the new mapping for allowed SeaDrop contracts.
                  for (uint256 i = 0; i < allowedSeaDropLength; ) {
                      _allowedSeaDrop[allowedSeaDrop[i]] = true;
                      unchecked {
                          ++i;
                      }
                  }
                  // Set the enumeration.
                  _enumeratedAllowedSeaDrop = allowedSeaDrop;
                  // Emit an event for the update.
                  emit AllowedSeaDropUpdated(allowedSeaDrop);
              }
              /**
               * @notice Burns `tokenId`. The caller must own `tokenId` or be an
               *         approved operator.
               *
               * @param tokenId The token id to burn.
               */
              // solhint-disable-next-line comprehensive-interface
              function burn(uint256 tokenId) external {
                  _burn(tokenId, true);
              }
              /**
               * @dev Overrides the `_startTokenId` function from ERC721A
               *      to start at token id `1`.
               *
               *      This is to avoid future possible problems since `0` is usually
               *      used to signal values that have not been set or have been removed.
               */
              function _startTokenId() internal view virtual override returns (uint256) {
                  return 1;
              }
              /**
               * @dev Overrides the `tokenURI()` function from ERC721A
               *      to return just the base URI if it is implied to not be a directory.
               *
               *      This is to help with ERC721 contracts in which the same token URI
               *      is desired for each token, such as when the tokenURI is 'unrevealed'.
               */
              function tokenURI(uint256 tokenId)
                  public
                  view
                  virtual
                  override
                  returns (string memory)
              {
                  if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
                  string memory baseURI = _baseURI();
                  // Exit early if the baseURI is empty.
                  if (bytes(baseURI).length == 0) {
                      return "";
                  }
                  // Check if the last character in baseURI is a slash.
                  if (bytes(baseURI)[bytes(baseURI).length - 1] != bytes("/")[0]) {
                      return baseURI;
                  }
                  return string(abi.encodePacked(baseURI, _toString(tokenId)));
              }
              /**
               * @notice Mint tokens, restricted to the SeaDrop contract.
               *
               * @dev    NOTE: If a token registers itself with multiple SeaDrop
               *         contracts, the implementation of this function should guard
               *         against reentrancy. If the implementing token uses
               *         _safeMint(), or a feeRecipient with a malicious receive() hook
               *         is specified, the token or fee recipients may be able to execute
               *         another mint in the same transaction via a separate SeaDrop
               *         contract.
               *         This is dangerous if an implementing token does not correctly
               *         update the minterNumMinted and currentTotalSupply values before
               *         transferring minted tokens, as SeaDrop references these values
               *         to enforce token limits on a per-wallet and per-stage basis.
               *
               *         ERC721A tracks these values automatically, but this note and
               *         nonReentrant modifier are left here to encourage best-practices
               *         when referencing this contract.
               *
               * @param minter   The address to mint to.
               * @param quantity The number of tokens to mint.
               */
              function mintSeaDrop(address minter, uint256 quantity)
                  external
                  virtual
                  override
                  nonReentrant
              {
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(msg.sender);
                  // Extra safety check to ensure the max supply is not exceeded.
                  if (_totalMinted() + quantity > maxSupply()) {
                      revert MintQuantityExceedsMaxSupply(
                          _totalMinted() + quantity,
                          maxSupply()
                      );
                  }
                  // Mint the quantity of tokens to the minter.
                  _safeMint(minter, quantity);
              }
              /**
               * @notice Update the public drop data for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl The allowed SeaDrop contract.
               * @param publicDrop  The public drop data.
               */
              function updatePublicDrop(
                  address seaDropImpl,
                  PublicDrop calldata publicDrop
              ) external virtual override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the public drop data on SeaDrop.
                  ISeaDrop(seaDropImpl).updatePublicDrop(publicDrop);
              }
              /**
               * @notice Update the allow list data for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl   The allowed SeaDrop contract.
               * @param allowListData The allow list data.
               */
              function updateAllowList(
                  address seaDropImpl,
                  AllowListData calldata allowListData
              ) external virtual override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the allow list on SeaDrop.
                  ISeaDrop(seaDropImpl).updateAllowList(allowListData);
              }
              /**
               * @notice Update the token gated drop stage data for this nft contract
               *         on SeaDrop.
               *         Only the owner can use this function.
               *
               *         Note: If two INonFungibleSeaDropToken tokens are doing
               *         simultaneous token gated drop promotions for each other,
               *         they can be minted by the same actor until
               *         `maxTokenSupplyForStage` is reached. Please ensure the
               *         `allowedNftToken` is not running an active drop during the
               *         `dropStage` time period.
               *
               * @param seaDropImpl     The allowed SeaDrop contract.
               * @param allowedNftToken The allowed nft token.
               * @param dropStage       The token gated drop stage data.
               */
              function updateTokenGatedDrop(
                  address seaDropImpl,
                  address allowedNftToken,
                  TokenGatedDropStage calldata dropStage
              ) external virtual override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the token gated drop stage.
                  ISeaDrop(seaDropImpl).updateTokenGatedDrop(allowedNftToken, dropStage);
              }
              /**
               * @notice Update the drop URI for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl The allowed SeaDrop contract.
               * @param dropURI     The new drop URI.
               */
              function updateDropURI(address seaDropImpl, string calldata dropURI)
                  external
                  virtual
                  override
              {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the drop URI.
                  ISeaDrop(seaDropImpl).updateDropURI(dropURI);
              }
              /**
               * @notice Update the creator payout address for this nft contract on
               *         SeaDrop.
               *         Only the owner can set the creator payout address.
               *
               * @param seaDropImpl   The allowed SeaDrop contract.
               * @param payoutAddress The new payout address.
               */
              function updateCreatorPayoutAddress(
                  address seaDropImpl,
                  address payoutAddress
              ) external {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the creator payout address.
                  ISeaDrop(seaDropImpl).updateCreatorPayoutAddress(payoutAddress);
              }
              /**
               * @notice Update the allowed fee recipient for this nft contract
               *         on SeaDrop.
               *         Only the owner can set the allowed fee recipient.
               *
               * @param seaDropImpl  The allowed SeaDrop contract.
               * @param feeRecipient The new fee recipient.
               * @param allowed      If the fee recipient is allowed.
               */
              function updateAllowedFeeRecipient(
                  address seaDropImpl,
                  address feeRecipient,
                  bool allowed
              ) external virtual {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the allowed fee recipient.
                  ISeaDrop(seaDropImpl).updateAllowedFeeRecipient(feeRecipient, allowed);
              }
              /**
               * @notice Update the server-side signers for this nft contract
               *         on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl                The allowed SeaDrop contract.
               * @param signer                     The signer to update.
               * @param signedMintValidationParams Minimum and maximum parameters to
               *                                   enforce for signed mints.
               */
              function updateSignedMintValidationParams(
                  address seaDropImpl,
                  address signer,
                  SignedMintValidationParams memory signedMintValidationParams
              ) external virtual override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the signer.
                  ISeaDrop(seaDropImpl).updateSignedMintValidationParams(
                      signer,
                      signedMintValidationParams
                  );
              }
              /**
               * @notice Update the allowed payers for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl The allowed SeaDrop contract.
               * @param payer       The payer to update.
               * @param allowed     Whether the payer is allowed.
               */
              function updatePayer(
                  address seaDropImpl,
                  address payer,
                  bool allowed
              ) external virtual override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the SeaDrop is allowed.
                  _onlyAllowedSeaDrop(seaDropImpl);
                  // Update the payer.
                  ISeaDrop(seaDropImpl).updatePayer(payer, allowed);
              }
              /**
               * @notice Returns a set of mint stats for the address.
               *         This assists SeaDrop in enforcing maxSupply,
               *         maxTotalMintableByWallet, and maxTokenSupplyForStage checks.
               *
               * @dev    NOTE: Implementing contracts should always update these numbers
               *         before transferring any tokens with _safeMint() to mitigate
               *         consequences of malicious onERC721Received() hooks.
               *
               * @param minter The minter address.
               */
              function getMintStats(address minter)
                  external
                  view
                  override
                  returns (
                      uint256 minterNumMinted,
                      uint256 currentTotalSupply,
                      uint256 maxSupply
                  )
              {
                  minterNumMinted = _numberMinted(minter);
                  currentTotalSupply = _totalMinted();
                  maxSupply = _maxSupply;
              }
              /**
               * @notice Returns whether the interface is supported.
               *
               * @param interfaceId The interface id to check against.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(IERC165, ERC721ContractMetadataCloneable)
                  returns (bool)
              {
                  return
                      interfaceId == type(INonFungibleSeaDropToken).interfaceId ||
                      interfaceId == type(ISeaDropTokenContractMetadata).interfaceId ||
                      // ERC721ContractMetadata returns supportsInterface true for
                      //     EIP-2981
                      // ERC721A returns supportsInterface true for
                      //     ERC165, ERC721, ERC721Metadata
                      super.supportsInterface(interfaceId);
              }
              /**
               * @notice Configure multiple properties at a time.
               *
               *         Note: The individual configure methods should be used
               *         to unset or reset any properties to zero, as this method
               *         will ignore zero-value properties in the config struct.
               *
               * @param config The configuration struct.
               */
              function multiConfigure(MultiConfigureStruct calldata config)
                  external
                  onlyOwner
              {
                  if (config.maxSupply > 0) {
                      this.setMaxSupply(config.maxSupply);
                  }
                  if (bytes(config.baseURI).length != 0) {
                      this.setBaseURI(config.baseURI);
                  }
                  if (bytes(config.contractURI).length != 0) {
                      this.setContractURI(config.contractURI);
                  }
                  if (
                      _cast(config.publicDrop.startTime != 0) |
                          _cast(config.publicDrop.endTime != 0) ==
                      1
                  ) {
                      this.updatePublicDrop(config.seaDropImpl, config.publicDrop);
                  }
                  if (bytes(config.dropURI).length != 0) {
                      this.updateDropURI(config.seaDropImpl, config.dropURI);
                  }
                  if (config.allowListData.merkleRoot != bytes32(0)) {
                      this.updateAllowList(config.seaDropImpl, config.allowListData);
                  }
                  if (config.creatorPayoutAddress != address(0)) {
                      this.updateCreatorPayoutAddress(
                          config.seaDropImpl,
                          config.creatorPayoutAddress
                      );
                  }
                  if (config.provenanceHash != bytes32(0)) {
                      this.setProvenanceHash(config.provenanceHash);
                  }
                  if (config.allowedFeeRecipients.length > 0) {
                      for (uint256 i = 0; i < config.allowedFeeRecipients.length; ) {
                          this.updateAllowedFeeRecipient(
                              config.seaDropImpl,
                              config.allowedFeeRecipients[i],
                              true
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.disallowedFeeRecipients.length > 0) {
                      for (uint256 i = 0; i < config.disallowedFeeRecipients.length; ) {
                          this.updateAllowedFeeRecipient(
                              config.seaDropImpl,
                              config.disallowedFeeRecipients[i],
                              false
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.allowedPayers.length > 0) {
                      for (uint256 i = 0; i < config.allowedPayers.length; ) {
                          this.updatePayer(
                              config.seaDropImpl,
                              config.allowedPayers[i],
                              true
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.disallowedPayers.length > 0) {
                      for (uint256 i = 0; i < config.disallowedPayers.length; ) {
                          this.updatePayer(
                              config.seaDropImpl,
                              config.disallowedPayers[i],
                              false
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.tokenGatedDropStages.length > 0) {
                      if (
                          config.tokenGatedDropStages.length !=
                          config.tokenGatedAllowedNftTokens.length
                      ) {
                          revert TokenGatedMismatch();
                      }
                      for (uint256 i = 0; i < config.tokenGatedDropStages.length; ) {
                          this.updateTokenGatedDrop(
                              config.seaDropImpl,
                              config.tokenGatedAllowedNftTokens[i],
                              config.tokenGatedDropStages[i]
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.disallowedTokenGatedAllowedNftTokens.length > 0) {
                      for (
                          uint256 i = 0;
                          i < config.disallowedTokenGatedAllowedNftTokens.length;
                      ) {
                          TokenGatedDropStage memory emptyStage;
                          this.updateTokenGatedDrop(
                              config.seaDropImpl,
                              config.disallowedTokenGatedAllowedNftTokens[i],
                              emptyStage
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.signedMintValidationParams.length > 0) {
                      if (
                          config.signedMintValidationParams.length !=
                          config.signers.length
                      ) {
                          revert SignersMismatch();
                      }
                      for (
                          uint256 i = 0;
                          i < config.signedMintValidationParams.length;
                      ) {
                          this.updateSignedMintValidationParams(
                              config.seaDropImpl,
                              config.signers[i],
                              config.signedMintValidationParams[i]
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
                  if (config.disallowedSigners.length > 0) {
                      for (uint256 i = 0; i < config.disallowedSigners.length; ) {
                          SignedMintValidationParams memory emptyParams;
                          this.updateSignedMintValidationParams(
                              config.seaDropImpl,
                              config.disallowedSigners[i],
                              emptyParams
                          );
                          unchecked {
                              ++i;
                          }
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import {
              ISeaDropTokenContractMetadata
          } from "../interfaces/ISeaDropTokenContractMetadata.sol";
          import {
              ERC721AConduitPreapprovedCloneable
          } from "./ERC721AConduitPreapprovedCloneable.sol";
          import { ERC721ACloneable } from "./ERC721ACloneable.sol";
          import { ERC721TransferValidator } from "../lib/ERC721TransferValidator.sol";
          import {
              ICreatorToken,
              ILegacyCreatorToken
          } from "../interfaces/ICreatorToken.sol";
          import { ITransferValidator721 } from "../interfaces/ITransferValidator.sol";
          import { TwoStepOwnable } from "utility-contracts/TwoStepOwnable.sol";
          import { IERC2981 } from "openzeppelin-contracts/interfaces/IERC2981.sol";
          import {
              IERC165
          } from "openzeppelin-contracts/utils/introspection/IERC165.sol";
          /**
           * @title  ERC721ContractMetadataCloneable
           * @author James Wenzel (emo.eth)
           * @author Ryan Ghods (ralxz.eth)
           * @author Stephan Min (stephanm.eth)
           * @notice ERC721ContractMetadata is a token contract that extends ERC721A
           *         with additional metadata and ownership capabilities.
           */
          contract ERC721ContractMetadataCloneable is
              ERC721AConduitPreapprovedCloneable,
              ERC721TransferValidator,
              TwoStepOwnable,
              ISeaDropTokenContractMetadata
          {
              /// @notice Track the max supply.
              uint256 _maxSupply;
              /// @notice Track the base URI for token metadata.
              string _tokenBaseURI;
              /// @notice Track the contract URI for contract metadata.
              string _contractURI;
              /// @notice Track the provenance hash for guaranteeing metadata order
              ///         for random reveals.
              bytes32 _provenanceHash;
              /// @notice Track the royalty info: address to receive royalties, and
              ///         royalty basis points.
              RoyaltyInfo _royaltyInfo;
              /**
               * @dev Reverts if the sender is not the owner or the contract itself.
               *      This function is inlined instead of being a modifier
               *      to save contract space from being inlined N times.
               */
              function _onlyOwnerOrSelf() internal view {
                  if (
                      _cast(msg.sender == owner()) | _cast(msg.sender == address(this)) ==
                      0
                  ) {
                      revert OnlyOwner();
                  }
              }
              /**
               * @notice Sets the base URI for the token metadata and emits an event.
               *
               * @param newBaseURI The new base URI to set.
               */
              function setBaseURI(string calldata newBaseURI) external override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Set the new base URI.
                  _tokenBaseURI = newBaseURI;
                  // Emit an event with the update.
                  if (totalSupply() != 0) {
                      emit BatchMetadataUpdate(1, _nextTokenId() - 1);
                  }
              }
              /**
               * @notice Sets the contract URI for contract metadata.
               *
               * @param newContractURI The new contract URI.
               */
              function setContractURI(string calldata newContractURI) external override {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Set the new contract URI.
                  _contractURI = newContractURI;
                  // Emit an event with the update.
                  emit ContractURIUpdated(newContractURI);
              }
              /**
               * @notice Emit an event notifying metadata updates for
               *         a range of token ids, according to EIP-4906.
               *
               * @param fromTokenId The start token id.
               * @param toTokenId   The end token id.
               */
              function emitBatchMetadataUpdate(uint256 fromTokenId, uint256 toTokenId)
                  external
              {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Emit an event with the update.
                  emit BatchMetadataUpdate(fromTokenId, toTokenId);
              }
              /**
               * @notice Sets the max token supply and emits an event.
               *
               * @param newMaxSupply The new max supply to set.
               */
              function setMaxSupply(uint256 newMaxSupply) external {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Ensure the max supply does not exceed the maximum value of uint64.
                  if (newMaxSupply > 2**64 - 1) {
                      revert CannotExceedMaxSupplyOfUint64(newMaxSupply);
                  }
                  // Set the new max supply.
                  _maxSupply = newMaxSupply;
                  // Emit an event with the update.
                  emit MaxSupplyUpdated(newMaxSupply);
              }
              /**
               * @notice Sets the provenance hash and emits an event.
               *
               *         The provenance hash is used for random reveals, which
               *         is a hash of the ordered metadata to show it has not been
               *         modified after mint started.
               *
               *         This function will revert after the first item has been minted.
               *
               * @param newProvenanceHash The new provenance hash to set.
               */
              function setProvenanceHash(bytes32 newProvenanceHash) external {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Revert if any items have been minted.
                  if (_totalMinted() > 0) {
                      revert ProvenanceHashCannotBeSetAfterMintStarted();
                  }
                  // Keep track of the old provenance hash for emitting with the event.
                  bytes32 oldProvenanceHash = _provenanceHash;
                  // Set the new provenance hash.
                  _provenanceHash = newProvenanceHash;
                  // Emit an event with the update.
                  emit ProvenanceHashUpdated(oldProvenanceHash, newProvenanceHash);
              }
              /**
               * @notice Sets the address and basis points for royalties.
               *
               * @param newInfo The struct to configure royalties.
               */
              function setRoyaltyInfo(RoyaltyInfo calldata newInfo) external {
                  // Ensure the sender is only the owner or contract itself.
                  _onlyOwnerOrSelf();
                  // Revert if the new royalty address is the zero address.
                  if (newInfo.royaltyAddress == address(0)) {
                      revert RoyaltyAddressCannotBeZeroAddress();
                  }
                  // Revert if the new basis points is greater than 10_000.
                  if (newInfo.royaltyBps > 10_000) {
                      revert InvalidRoyaltyBasisPoints(newInfo.royaltyBps);
                  }
                  // Set the new royalty info.
                  _royaltyInfo = newInfo;
                  // Emit an event with the updated params.
                  emit RoyaltyInfoUpdated(newInfo.royaltyAddress, newInfo.royaltyBps);
              }
              /**
               * @notice Returns the base URI for token metadata.
               */
              function baseURI() external view override returns (string memory) {
                  return _baseURI();
              }
              /**
               * @notice Returns the base URI for the contract, which ERC721A uses
               *         to return tokenURI.
               */
              function _baseURI() internal view virtual override returns (string memory) {
                  return _tokenBaseURI;
              }
              /**
               * @notice Returns the contract URI for contract metadata.
               */
              function contractURI() external view override returns (string memory) {
                  return _contractURI;
              }
              /**
               * @notice Returns the max token supply.
               */
              function maxSupply() public view returns (uint256) {
                  return _maxSupply;
              }
              /**
               * @notice Returns the provenance hash.
               *         The provenance hash is used for random reveals, which
               *         is a hash of the ordered metadata to show it is unmodified
               *         after mint has started.
               */
              function provenanceHash() external view override returns (bytes32) {
                  return _provenanceHash;
              }
              /**
               * @notice Returns the address that receives royalties.
               */
              function royaltyAddress() external view returns (address) {
                  return _royaltyInfo.royaltyAddress;
              }
              /**
               * @notice Returns the royalty basis points out of 10_000.
               */
              function royaltyBasisPoints() external view returns (uint256) {
                  return _royaltyInfo.royaltyBps;
              }
              /**
               * @notice Called with the sale price to determine how much royalty
               *         is owed and to whom.
               *
               * @ param  _tokenId     The NFT asset queried for royalty information.
               * @param  _salePrice    The sale price of the NFT asset specified by
               *                       _tokenId.
               *
               * @return receiver      Address of who should be sent the royalty payment.
               * @return royaltyAmount The royalty payment amount for _salePrice.
               */
              function royaltyInfo(
                  uint256,
                  /* _tokenId */
                  uint256 _salePrice
              ) external view returns (address receiver, uint256 royaltyAmount) {
                  // Put the royalty info on the stack for more efficient access.
                  RoyaltyInfo storage info = _royaltyInfo;
                  // Set the royalty amount to the sale price times the royalty basis
                  // points divided by 10_000.
                  royaltyAmount = (_salePrice * info.royaltyBps) / 10_000;
                  // Set the receiver of the royalty.
                  receiver = info.royaltyAddress;
              }
              /**
               * @notice Returns the transfer validation function used.
               */
              function getTransferValidationFunction()
                  external
                  pure
                  returns (bytes4 functionSignature, bool isViewFunction)
              {
                  functionSignature = ITransferValidator721.validateTransfer.selector;
                  isViewFunction = false;
              }
              /**
               * @notice Set the transfer validator. Only callable by the token owner.
               */
              function setTransferValidator(address newValidator) external onlyOwner {
                  // Set the new transfer validator.
                  _setTransferValidator(newValidator);
              }
              /**
               * @dev Hook that is called before any token transfer.
               *      This includes minting and burning.
               */
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 /* quantity */
              ) internal virtual override {
                  if (from != address(0) && to != address(0)) {
                      // Call the transfer validator if one is set.
                      address transferValidator = _transferValidator;
                      if (transferValidator != address(0)) {
                          ITransferValidator721(transferValidator).validateTransfer(
                              msg.sender,
                              from,
                              to,
                              startTokenId
                          );
                      }
                  }
              }
              /**
               * @notice Returns whether the interface is supported.
               *
               * @param interfaceId The interface id to check against.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(IERC165, ERC721ACloneable)
                  returns (bool)
              {
                  return
                      interfaceId == type(IERC2981).interfaceId ||
                      interfaceId == type(ICreatorToken).interfaceId ||
                      interfaceId == type(ILegacyCreatorToken).interfaceId ||
                      interfaceId == 0x49064906 || // ERC-4906
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev Internal pure function to cast a `bool` value to a `uint256` value.
               *
               * @param b The `bool` value to cast.
               *
               * @return u The `uint256` value.
               */
              function _cast(bool b) internal pure returns (uint256 u) {
                  assembly {
                      u := b
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import {
              ISeaDropTokenContractMetadata
          } from "./ISeaDropTokenContractMetadata.sol";
          import {
              AllowListData,
              PublicDrop,
              TokenGatedDropStage,
              SignedMintValidationParams
          } from "../lib/SeaDropStructs.sol";
          interface INonFungibleSeaDropToken is ISeaDropTokenContractMetadata {
              /**
               * @dev Revert with an error if a contract is not an allowed
               *      SeaDrop address.
               */
              error OnlyAllowedSeaDrop();
              /**
               * @dev Emit an event when allowed SeaDrop contracts are updated.
               */
              event AllowedSeaDropUpdated(address[] allowedSeaDrop);
              /**
               * @notice Update the allowed SeaDrop contracts.
               *         Only the owner can use this function.
               *
               * @param allowedSeaDrop The allowed SeaDrop addresses.
               */
              function updateAllowedSeaDrop(address[] calldata allowedSeaDrop) external;
              /**
               * @notice Mint tokens, restricted to the SeaDrop contract.
               *
               * @dev    NOTE: If a token registers itself with multiple SeaDrop
               *         contracts, the implementation of this function should guard
               *         against reentrancy. If the implementing token uses
               *         _safeMint(), or a feeRecipient with a malicious receive() hook
               *         is specified, the token or fee recipients may be able to execute
               *         another mint in the same transaction via a separate SeaDrop
               *         contract.
               *         This is dangerous if an implementing token does not correctly
               *         update the minterNumMinted and currentTotalSupply values before
               *         transferring minted tokens, as SeaDrop references these values
               *         to enforce token limits on a per-wallet and per-stage basis.
               *
               * @param minter   The address to mint to.
               * @param quantity The number of tokens to mint.
               */
              function mintSeaDrop(address minter, uint256 quantity) external;
              /**
               * @notice Returns a set of mint stats for the address.
               *         This assists SeaDrop in enforcing maxSupply,
               *         maxTotalMintableByWallet, and maxTokenSupplyForStage checks.
               *
               * @dev    NOTE: Implementing contracts should always update these numbers
               *         before transferring any tokens with _safeMint() to mitigate
               *         consequences of malicious onERC721Received() hooks.
               *
               * @param minter The minter address.
               */
              function getMintStats(address minter)
                  external
                  view
                  returns (
                      uint256 minterNumMinted,
                      uint256 currentTotalSupply,
                      uint256 maxSupply
                  );
              /**
               * @notice Update the public drop data for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl The allowed SeaDrop contract.
               * @param publicDrop  The public drop data.
               */
              function updatePublicDrop(
                  address seaDropImpl,
                  PublicDrop calldata publicDrop
              ) external;
              /**
               * @notice Update the allow list data for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl   The allowed SeaDrop contract.
               * @param allowListData The allow list data.
               */
              function updateAllowList(
                  address seaDropImpl,
                  AllowListData calldata allowListData
              ) external;
              /**
               * @notice Update the token gated drop stage data for this nft contract
               *         on SeaDrop.
               *         Only the owner can use this function.
               *
               *         Note: If two INonFungibleSeaDropToken tokens are doing
               *         simultaneous token gated drop promotions for each other,
               *         they can be minted by the same actor until
               *         `maxTokenSupplyForStage` is reached. Please ensure the
               *         `allowedNftToken` is not running an active drop during the
               *         `dropStage` time period.
               *
               *
               * @param seaDropImpl     The allowed SeaDrop contract.
               * @param allowedNftToken The allowed nft token.
               * @param dropStage       The token gated drop stage data.
               */
              function updateTokenGatedDrop(
                  address seaDropImpl,
                  address allowedNftToken,
                  TokenGatedDropStage calldata dropStage
              ) external;
              /**
               * @notice Update the drop URI for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl The allowed SeaDrop contract.
               * @param dropURI     The new drop URI.
               */
              function updateDropURI(address seaDropImpl, string calldata dropURI)
                  external;
              /**
               * @notice Update the creator payout address for this nft contract on
               *         SeaDrop.
               *         Only the owner can set the creator payout address.
               *
               * @param seaDropImpl   The allowed SeaDrop contract.
               * @param payoutAddress The new payout address.
               */
              function updateCreatorPayoutAddress(
                  address seaDropImpl,
                  address payoutAddress
              ) external;
              /**
               * @notice Update the allowed fee recipient for this nft contract
               *         on SeaDrop.
               *
               * @param seaDropImpl  The allowed SeaDrop contract.
               * @param feeRecipient The new fee recipient.
               */
              function updateAllowedFeeRecipient(
                  address seaDropImpl,
                  address feeRecipient,
                  bool allowed
              ) external;
              /**
               * @notice Update the server-side signers for this nft contract
               *         on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl                The allowed SeaDrop contract.
               * @param signer                     The signer to update.
               * @param signedMintValidationParams Minimum and maximum parameters
               *                                   to enforce for signed mints.
               */
              function updateSignedMintValidationParams(
                  address seaDropImpl,
                  address signer,
                  SignedMintValidationParams memory signedMintValidationParams
              ) external;
              /**
               * @notice Update the allowed payers for this nft contract on SeaDrop.
               *         Only the owner can use this function.
               *
               * @param seaDropImpl The allowed SeaDrop contract.
               * @param payer       The payer to update.
               * @param allowed     Whether the payer is allowed.
               */
              function updatePayer(
                  address seaDropImpl,
                  address payer,
                  bool allowed
              ) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import {
              AllowListData,
              MintParams,
              PublicDrop,
              TokenGatedDropStage,
              TokenGatedMintParams,
              SignedMintValidationParams
          } from "../lib/SeaDropStructs.sol";
          import { SeaDropErrorsAndEvents } from "../lib/SeaDropErrorsAndEvents.sol";
          interface ISeaDrop is SeaDropErrorsAndEvents {
              /**
               * @notice Mint a public drop.
               *
               * @param nftContract      The nft contract to mint.
               * @param feeRecipient     The fee recipient.
               * @param minterIfNotPayer The mint recipient if different than the payer.
               * @param quantity         The number of tokens to mint.
               */
              function mintPublic(
                  address nftContract,
                  address feeRecipient,
                  address minterIfNotPayer,
                  uint256 quantity
              ) external payable;
              /**
               * @notice Mint from an allow list.
               *
               * @param nftContract      The nft contract to mint.
               * @param feeRecipient     The fee recipient.
               * @param minterIfNotPayer The mint recipient if different than the payer.
               * @param quantity         The number of tokens to mint.
               * @param mintParams       The mint parameters.
               * @param proof            The proof for the leaf of the allow list.
               */
              function mintAllowList(
                  address nftContract,
                  address feeRecipient,
                  address minterIfNotPayer,
                  uint256 quantity,
                  MintParams calldata mintParams,
                  bytes32[] calldata proof
              ) external payable;
              /**
               * @notice Mint with a server-side signature.
               *         Note that a signature can only be used once.
               *
               * @param nftContract      The nft contract to mint.
               * @param feeRecipient     The fee recipient.
               * @param minterIfNotPayer The mint recipient if different than the payer.
               * @param quantity         The number of tokens to mint.
               * @param mintParams       The mint parameters.
               * @param salt             The sale for the signed mint.
               * @param signature        The server-side signature, must be an allowed
               *                         signer.
               */
              function mintSigned(
                  address nftContract,
                  address feeRecipient,
                  address minterIfNotPayer,
                  uint256 quantity,
                  MintParams calldata mintParams,
                  uint256 salt,
                  bytes calldata signature
              ) external payable;
              /**
               * @notice Mint as an allowed token holder.
               *         This will mark the token id as redeemed and will revert if the
               *         same token id is attempted to be redeemed twice.
               *
               * @param nftContract      The nft contract to mint.
               * @param feeRecipient     The fee recipient.
               * @param minterIfNotPayer The mint recipient if different than the payer.
               * @param mintParams       The token gated mint params.
               */
              function mintAllowedTokenHolder(
                  address nftContract,
                  address feeRecipient,
                  address minterIfNotPayer,
                  TokenGatedMintParams calldata mintParams
              ) external payable;
              /**
               * @notice Emits an event to notify update of the drop URI.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param dropURI The new drop URI.
               */
              function updateDropURI(string calldata dropURI) external;
              /**
               * @notice Updates the public drop data for the nft contract
               *         and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param publicDrop The public drop data.
               */
              function updatePublicDrop(PublicDrop calldata publicDrop) external;
              /**
               * @notice Updates the allow list merkle root for the nft contract
               *         and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param allowListData The allow list data.
               */
              function updateAllowList(AllowListData calldata allowListData) external;
              /**
               * @notice Updates the token gated drop stage for the nft contract
               *         and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               *         Note: If two INonFungibleSeaDropToken tokens are doing
               *         simultaneous token gated drop promotions for each other,
               *         they can be minted by the same actor until
               *         `maxTokenSupplyForStage` is reached. Please ensure the
               *         `allowedNftToken` is not running an active drop during
               *         the `dropStage` time period.
               *
               * @param allowedNftToken The token gated nft token.
               * @param dropStage       The token gated drop stage data.
               */
              function updateTokenGatedDrop(
                  address allowedNftToken,
                  TokenGatedDropStage calldata dropStage
              ) external;
              /**
               * @notice Updates the creator payout address and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param payoutAddress The creator payout address.
               */
              function updateCreatorPayoutAddress(address payoutAddress) external;
              /**
               * @notice Updates the allowed fee recipient and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param feeRecipient The fee recipient.
               * @param allowed      If the fee recipient is allowed.
               */
              function updateAllowedFeeRecipient(address feeRecipient, bool allowed)
                  external;
              /**
               * @notice Updates the allowed server-side signers and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param signer                     The signer to update.
               * @param signedMintValidationParams Minimum and maximum parameters
               *                                   to enforce for signed mints.
               */
              function updateSignedMintValidationParams(
                  address signer,
                  SignedMintValidationParams calldata signedMintValidationParams
              ) external;
              /**
               * @notice Updates the allowed payer and emits an event.
               *
               *         This method assume msg.sender is an nft contract and its
               *         ERC165 interface id matches INonFungibleSeaDropToken.
               *
               *         Note: Be sure only authorized users can call this from
               *         token contracts that implement INonFungibleSeaDropToken.
               *
               * @param payer   The payer to add or remove.
               * @param allowed Whether to add or remove the payer.
               */
              function updatePayer(address payer, bool allowed) external;
              /**
               * @notice Returns the public drop data for the nft contract.
               *
               * @param nftContract The nft contract.
               */
              function getPublicDrop(address nftContract)
                  external
                  view
                  returns (PublicDrop memory);
              /**
               * @notice Returns the creator payout address for the nft contract.
               *
               * @param nftContract The nft contract.
               */
              function getCreatorPayoutAddress(address nftContract)
                  external
                  view
                  returns (address);
              /**
               * @notice Returns the allow list merkle root for the nft contract.
               *
               * @param nftContract The nft contract.
               */
              function getAllowListMerkleRoot(address nftContract)
                  external
                  view
                  returns (bytes32);
              /**
               * @notice Returns if the specified fee recipient is allowed
               *         for the nft contract.
               *
               * @param nftContract  The nft contract.
               * @param feeRecipient The fee recipient.
               */
              function getFeeRecipientIsAllowed(address nftContract, address feeRecipient)
                  external
                  view
                  returns (bool);
              /**
               * @notice Returns an enumeration of allowed fee recipients for an
               *         nft contract when fee recipients are enforced
               *
               * @param nftContract The nft contract.
               */
              function getAllowedFeeRecipients(address nftContract)
                  external
                  view
                  returns (address[] memory);
              /**
               * @notice Returns the server-side signers for the nft contract.
               *
               * @param nftContract The nft contract.
               */
              function getSigners(address nftContract)
                  external
                  view
                  returns (address[] memory);
              /**
               * @notice Returns the struct of SignedMintValidationParams for a signer.
               *
               * @param nftContract The nft contract.
               * @param signer      The signer.
               */
              function getSignedMintValidationParams(address nftContract, address signer)
                  external
                  view
                  returns (SignedMintValidationParams memory);
              /**
               * @notice Returns the payers for the nft contract.
               *
               * @param nftContract The nft contract.
               */
              function getPayers(address nftContract)
                  external
                  view
                  returns (address[] memory);
              /**
               * @notice Returns if the specified payer is allowed
               *         for the nft contract.
               *
               * @param nftContract The nft contract.
               * @param payer       The payer.
               */
              function getPayerIsAllowed(address nftContract, address payer)
                  external
                  view
                  returns (bool);
              /**
               * @notice Returns the allowed token gated drop tokens for the nft contract.
               *
               * @param nftContract The nft contract.
               */
              function getTokenGatedAllowedTokens(address nftContract)
                  external
                  view
                  returns (address[] memory);
              /**
               * @notice Returns the token gated drop data for the nft contract
               *         and token gated nft.
               *
               * @param nftContract     The nft contract.
               * @param allowedNftToken The token gated nft token.
               */
              function getTokenGatedDrop(address nftContract, address allowedNftToken)
                  external
                  view
                  returns (TokenGatedDropStage memory);
              /**
               * @notice Returns whether the token id for a token gated drop has been
               *         redeemed.
               *
               * @param nftContract       The nft contract.
               * @param allowedNftToken   The token gated nft token.
               * @param allowedNftTokenId The token gated nft token id to check.
               */
              function getAllowedNftTokenIdIsRedeemed(
                  address nftContract,
                  address allowedNftToken,
                  uint256 allowedNftTokenId
              ) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          /**
           * @notice A struct defining public drop data.
           *         Designed to fit efficiently in one storage slot.
           * 
           * @param mintPrice                The mint price per token. (Up to 1.2m
           *                                 of native token, e.g. ETH, MATIC)
           * @param startTime                The start time, ensure this is not zero.
           * @param endTIme                  The end time, ensure this is not zero.
           * @param maxTotalMintableByWallet Maximum total number of mints a user is
           *                                 allowed. (The limit for this field is
           *                                 2^16 - 1)
           * @param feeBps                   Fee out of 10_000 basis points to be
           *                                 collected.
           * @param restrictFeeRecipients    If false, allow any fee recipient;
           *                                 if true, check fee recipient is allowed.
           */
          struct PublicDrop {
              uint80 mintPrice; // 80/256 bits
              uint48 startTime; // 128/256 bits
              uint48 endTime; // 176/256 bits
              uint16 maxTotalMintableByWallet; // 224/256 bits
              uint16 feeBps; // 240/256 bits
              bool restrictFeeRecipients; // 248/256 bits
          }
          /**
           * @notice A struct defining token gated drop stage data.
           *         Designed to fit efficiently in one storage slot.
           * 
           * @param mintPrice                The mint price per token. (Up to 1.2m 
           *                                 of native token, e.g.: ETH, MATIC)
           * @param maxTotalMintableByWallet Maximum total number of mints a user is
           *                                 allowed. (The limit for this field is
           *                                 2^16 - 1)
           * @param startTime                The start time, ensure this is not zero.
           * @param endTime                  The end time, ensure this is not zero.
           * @param dropStageIndex           The drop stage index to emit with the event
           *                                 for analytical purposes. This should be 
           *                                 non-zero since the public mint emits
           *                                 with index zero.
           * @param maxTokenSupplyForStage   The limit of token supply this stage can
           *                                 mint within. (The limit for this field is
           *                                 2^16 - 1)
           * @param feeBps                   Fee out of 10_000 basis points to be
           *                                 collected.
           * @param restrictFeeRecipients    If false, allow any fee recipient;
           *                                 if true, check fee recipient is allowed.
           */
          struct TokenGatedDropStage {
              uint80 mintPrice; // 80/256 bits
              uint16 maxTotalMintableByWallet; // 96/256 bits
              uint48 startTime; // 144/256 bits
              uint48 endTime; // 192/256 bits
              uint8 dropStageIndex; // non-zero. 200/256 bits
              uint32 maxTokenSupplyForStage; // 232/256 bits
              uint16 feeBps; // 248/256 bits
              bool restrictFeeRecipients; // 256/256 bits
          }
          /**
           * @notice A struct defining mint params for an allow list.
           *         An allow list leaf will be composed of `msg.sender` and
           *         the following params.
           * 
           *         Note: Since feeBps is encoded in the leaf, backend should ensure
           *         that feeBps is acceptable before generating a proof.
           * 
           * @param mintPrice                The mint price per token.
           * @param maxTotalMintableByWallet Maximum total number of mints a user is
           *                                 allowed.
           * @param startTime                The start time, ensure this is not zero.
           * @param endTime                  The end time, ensure this is not zero.
           * @param dropStageIndex           The drop stage index to emit with the event
           *                                 for analytical purposes. This should be
           *                                 non-zero since the public mint emits with
           *                                 index zero.
           * @param maxTokenSupplyForStage   The limit of token supply this stage can
           *                                 mint within.
           * @param feeBps                   Fee out of 10_000 basis points to be
           *                                 collected.
           * @param restrictFeeRecipients    If false, allow any fee recipient;
           *                                 if true, check fee recipient is allowed.
           */
          struct MintParams {
              uint256 mintPrice; 
              uint256 maxTotalMintableByWallet;
              uint256 startTime;
              uint256 endTime;
              uint256 dropStageIndex; // non-zero
              uint256 maxTokenSupplyForStage;
              uint256 feeBps;
              bool restrictFeeRecipients;
          }
          /**
           * @notice A struct defining token gated mint params.
           * 
           * @param allowedNftToken    The allowed nft token contract address.
           * @param allowedNftTokenIds The token ids to redeem.
           */
          struct TokenGatedMintParams {
              address allowedNftToken;
              uint256[] allowedNftTokenIds;
          }
          /**
           * @notice A struct defining allow list data (for minting an allow list).
           * 
           * @param merkleRoot    The merkle root for the allow list.
           * @param publicKeyURIs If the allowListURI is encrypted, a list of URIs
           *                      pointing to the public keys. Empty if unencrypted.
           * @param allowListURI  The URI for the allow list.
           */
          struct AllowListData {
              bytes32 merkleRoot;
              string[] publicKeyURIs;
              string allowListURI;
          }
          /**
           * @notice A struct defining minimum and maximum parameters to validate for 
           *         signed mints, to minimize negative effects of a compromised signer.
           *
           * @param minMintPrice                The minimum mint price allowed.
           * @param maxMaxTotalMintableByWallet The maximum total number of mints allowed
           *                                    by a wallet.
           * @param minStartTime                The minimum start time allowed.
           * @param maxEndTime                  The maximum end time allowed.
           * @param maxMaxTokenSupplyForStage   The maximum token supply allowed.
           * @param minFeeBps                   The minimum fee allowed.
           * @param maxFeeBps                   The maximum fee allowed.
           */
          struct SignedMintValidationParams {
              uint80 minMintPrice; // 80/256 bits
              uint24 maxMaxTotalMintableByWallet; // 104/256 bits
              uint40 minStartTime; // 144/256 bits
              uint40 maxEndTime; // 184/256 bits
              uint40 maxMaxTokenSupplyForStage; // 224/256 bits
              uint16 minFeeBps; // 240/256 bits
              uint16 maxFeeBps; // 256/256 bits
          }// SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import {
            AllowListData,
            PublicDrop,
            SignedMintValidationParams,
            TokenGatedDropStage
          } from "./SeaDropStructs.sol";
          interface ERC721SeaDropStructsErrorsAndEvents {
            /**
             * @notice Revert with an error if mint exceeds the max supply.
             */
            error MintQuantityExceedsMaxSupply(uint256 total, uint256 maxSupply);
            /**
             * @notice Revert with an error if the number of token gated 
             *         allowedNftTokens doesn't match the length of supplied
             *         drop stages.
             */
            error TokenGatedMismatch();
            /**
             *  @notice Revert with an error if the number of signers doesn't match
             *          the length of supplied signedMintValidationParams
             */
            error SignersMismatch();
            /**
             * @notice An event to signify that a SeaDrop token contract was deployed.
             */
            event SeaDropTokenDeployed();
            /**
             * @notice A struct to configure multiple contract options at a time.
             */
            struct MultiConfigureStruct {
              uint256 maxSupply;
              string baseURI;
              string contractURI;
              address seaDropImpl;
              PublicDrop publicDrop;
              string dropURI;
              AllowListData allowListData;
              address creatorPayoutAddress;
              bytes32 provenanceHash;
              address[] allowedFeeRecipients;
              address[] disallowedFeeRecipients;
              address[] allowedPayers;
              address[] disallowedPayers;
              // Token-gated
              address[] tokenGatedAllowedNftTokens;
              TokenGatedDropStage[] tokenGatedDropStages;
              address[] disallowedTokenGatedAllowedNftTokens;
              // Server-signed
              address[] signers;
              SignedMintValidationParams[] signedMintValidationParams;
              address[] disallowedSigners;
            }
          }// SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.2
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import { IERC721A } from "ERC721A/IERC721A.sol";
          import {
              Initializable
          } from "openzeppelin-contracts-upgradeable/proxy/utils/Initializable.sol";
          /**
           * @dev Interface of ERC721 token receiver.
           */
          interface ERC721A__IERC721Receiver {
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          /**
           * @title ERC721A
           *
           * @dev Implementation of the [ERC721](https://eips.ethereum.org/EIPS/eip-721)
           * Non-Fungible Token Standard, including the Metadata extension.
           * Optimized for lower gas during batch mints.
           *
           * Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...)
           * starting from `_startTokenId()`.
           *
           * Assumptions:
           *
           * - An owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
           * - The maximum token ID cannot exceed 2**256 - 1 (max value of uint256).
           */
          contract ERC721ACloneable is IERC721A, Initializable {
              // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
              struct TokenApprovalRef {
                  address value;
              }
              // =============================================================
              //                           CONSTANTS
              // =============================================================
              // Mask of an entry in packed address data.
              uint256 private constant _BITMASK_ADDRESS_DATA_ENTRY = (1 << 64) - 1;
              // The bit position of `numberMinted` in packed address data.
              uint256 private constant _BITPOS_NUMBER_MINTED = 64;
              // The bit position of `numberBurned` in packed address data.
              uint256 private constant _BITPOS_NUMBER_BURNED = 128;
              // The bit position of `aux` in packed address data.
              uint256 private constant _BITPOS_AUX = 192;
              // Mask of all 256 bits in packed address data except the 64 bits for `aux`.
              uint256 private constant _BITMASK_AUX_COMPLEMENT = (1 << 192) - 1;
              // The bit position of `startTimestamp` in packed ownership.
              uint256 private constant _BITPOS_START_TIMESTAMP = 160;
              // The bit mask of the `burned` bit in packed ownership.
              uint256 private constant _BITMASK_BURNED = 1 << 224;
              // The bit position of the `nextInitialized` bit in packed ownership.
              uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;
              // The bit mask of the `nextInitialized` bit in packed ownership.
              uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;
              // The bit position of `extraData` in packed ownership.
              uint256 private constant _BITPOS_EXTRA_DATA = 232;
              // Mask of all 256 bits in a packed ownership except the 24 bits for `extraData`.
              uint256 private constant _BITMASK_EXTRA_DATA_COMPLEMENT = (1 << 232) - 1;
              // The mask of the lower 160 bits for addresses.
              uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;
              // The maximum `quantity` that can be minted with {_mintERC2309}.
              // This limit is to prevent overflows on the address data entries.
              // For a limit of 5000, a total of 3.689e15 calls to {_mintERC2309}
              // is required to cause an overflow, which is unrealistic.
              uint256 private constant _MAX_MINT_ERC2309_QUANTITY_LIMIT = 5000;
              // The `Transfer` event signature is given by:
              // `keccak256(bytes("Transfer(address,address,uint256)"))`.
              bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
                  0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
              // =============================================================
              //                            STORAGE
              // =============================================================
              // The next token ID to be minted.
              uint256 private _currentIndex;
              // The number of tokens burned.
              uint256 private _burnCounter;
              // Token name
              string private _name;
              // Token symbol
              string private _symbol;
              // Mapping from token ID to ownership details
              // An empty struct value does not necessarily mean the token is unowned.
              // See {_packedOwnershipOf} implementation for details.
              //
              // Bits Layout:
              // - [0..159]   `addr`
              // - [160..223] `startTimestamp`
              // - [224]      `burned`
              // - [225]      `nextInitialized`
              // - [232..255] `extraData`
              mapping(uint256 => uint256) private _packedOwnerships;
              // Mapping owner address to address data.
              //
              // Bits Layout:
              // - [0..63]    `balance`
              // - [64..127]  `numberMinted`
              // - [128..191] `numberBurned`
              // - [192..255] `aux`
              mapping(address => uint256) private _packedAddressData;
              // Mapping from token ID to approved address.
              mapping(uint256 => TokenApprovalRef) private _tokenApprovals;
              // Mapping from owner to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              // =============================================================
              //                          CONSTRUCTOR
              // =============================================================
              function __ERC721ACloneable__init(
                  string memory name_,
                  string memory symbol_
              ) internal onlyInitializing {
                  _name = name_;
                  _symbol = symbol_;
                  _currentIndex = _startTokenId();
              }
              // =============================================================
              //                   TOKEN COUNTING OPERATIONS
              // =============================================================
              /**
               * @dev Returns the starting token ID.
               * To change the starting token ID, please override this function.
               */
              function _startTokenId() internal view virtual returns (uint256) {
                  return 0;
              }
              /**
               * @dev Returns the next token ID to be minted.
               */
              function _nextTokenId() internal view virtual returns (uint256) {
                  return _currentIndex;
              }
              /**
               * @dev Returns the total number of tokens in existence.
               * Burned tokens will reduce the count.
               * To get the total number of tokens minted, please see {_totalMinted}.
               */
              function totalSupply() public view virtual override returns (uint256) {
                  // Counter underflow is impossible as _burnCounter cannot be incremented
                  // more than `_currentIndex - _startTokenId()` times.
                  unchecked {
                      return _currentIndex - _burnCounter - _startTokenId();
                  }
              }
              /**
               * @dev Returns the total amount of tokens minted in the contract.
               */
              function _totalMinted() internal view virtual returns (uint256) {
                  // Counter underflow is impossible as `_currentIndex` does not decrement,
                  // and it is initialized to `_startTokenId()`.
                  unchecked {
                      return _currentIndex - _startTokenId();
                  }
              }
              /**
               * @dev Returns the total number of tokens burned.
               */
              function _totalBurned() internal view virtual returns (uint256) {
                  return _burnCounter;
              }
              // =============================================================
              //                    ADDRESS DATA OPERATIONS
              // =============================================================
              /**
               * @dev Returns the number of tokens in `owner`'s account.
               */
              function balanceOf(address owner)
                  public
                  view
                  virtual
                  override
                  returns (uint256)
              {
                  if (owner == address(0)) revert BalanceQueryForZeroAddress();
                  return _packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
              }
              /**
               * Returns the number of tokens minted by `owner`.
               */
              function _numberMinted(address owner) internal view returns (uint256) {
                  return
                      (_packedAddressData[owner] >> _BITPOS_NUMBER_MINTED) &
                      _BITMASK_ADDRESS_DATA_ENTRY;
              }
              /**
               * Returns the number of tokens burned by or on behalf of `owner`.
               */
              function _numberBurned(address owner) internal view returns (uint256) {
                  return
                      (_packedAddressData[owner] >> _BITPOS_NUMBER_BURNED) &
                      _BITMASK_ADDRESS_DATA_ENTRY;
              }
              /**
               * Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
               */
              function _getAux(address owner) internal view returns (uint64) {
                  return uint64(_packedAddressData[owner] >> _BITPOS_AUX);
              }
              /**
               * Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
               * If there are multiple variables, please pack them into a uint64.
               */
              function _setAux(address owner, uint64 aux) internal virtual {
                  uint256 packed = _packedAddressData[owner];
                  uint256 auxCasted;
                  // Cast `aux` with assembly to avoid redundant masking.
                  assembly {
                      auxCasted := aux
                  }
                  packed =
                      (packed & _BITMASK_AUX_COMPLEMENT) |
                      (auxCasted << _BITPOS_AUX);
                  _packedAddressData[owner] = packed;
              }
              // =============================================================
              //                            IERC165
              // =============================================================
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30000 gas.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override
                  returns (bool)
              {
                  // The interface IDs are constants representing the first 4 bytes
                  // of the XOR of all function selectors in the interface.
                  // See: [ERC165](https://eips.ethereum.org/EIPS/eip-165)
                  // (e.g. `bytes4(i.functionA.selector ^ i.functionB.selector ^ ...)`)
                  return
                      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
                      interfaceId == 0x80ac58cd || // ERC165 interface ID for ERC721.
                      interfaceId == 0x5b5e139f; // ERC165 interface ID for ERC721Metadata.
              }
              // =============================================================
              //                        IERC721Metadata
              // =============================================================
              /**
               * @dev Returns the token collection name.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId)
                  public
                  view
                  virtual
                  override
                  returns (string memory)
              {
                  if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
                  string memory baseURI = _baseURI();
                  return
                      bytes(baseURI).length != 0
                          ? string(abi.encodePacked(baseURI, _toString(tokenId)))
                          : "";
              }
              /**
               * @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, it can be overridden in child contracts.
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return "";
              }
              // =============================================================
              //                     OWNERSHIPS OPERATIONS
              // =============================================================
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId)
                  public
                  view
                  virtual
                  override
                  returns (address)
              {
                  return address(uint160(_packedOwnershipOf(tokenId)));
              }
              /**
               * @dev Gas spent here starts off proportional to the maximum mint batch size.
               * It gradually moves to O(1) as tokens get transferred around over time.
               */
              function _ownershipOf(uint256 tokenId)
                  internal
                  view
                  virtual
                  returns (TokenOwnership memory)
              {
                  return _unpackedOwnership(_packedOwnershipOf(tokenId));
              }
              /**
               * @dev Returns the unpacked `TokenOwnership` struct at `index`.
               */
              function _ownershipAt(uint256 index)
                  internal
                  view
                  virtual
                  returns (TokenOwnership memory)
              {
                  return _unpackedOwnership(_packedOwnerships[index]);
              }
              /**
               * @dev Initializes the ownership slot minted at `index` for efficiency purposes.
               */
              function _initializeOwnershipAt(uint256 index) internal virtual {
                  if (_packedOwnerships[index] == 0) {
                      _packedOwnerships[index] = _packedOwnershipOf(index);
                  }
              }
              /**
               * Returns the packed ownership data of `tokenId`.
               */
              function _packedOwnershipOf(uint256 tokenId)
                  private
                  view
                  returns (uint256)
              {
                  uint256 curr = tokenId;
                  unchecked {
                      if (_startTokenId() <= curr) {
                          if (curr < _currentIndex) {
                              uint256 packed = _packedOwnerships[curr];
                              // If not burned.
                              if (packed & _BITMASK_BURNED == 0) {
                                  // Invariant:
                                  // There will always be an initialized ownership slot
                                  // (i.e. `ownership.addr != address(0) && ownership.burned == false`)
                                  // before an unintialized ownership slot
                                  // (i.e. `ownership.addr == address(0) && ownership.burned == false`)
                                  // Hence, `curr` will not underflow.
                                  //
                                  // We can directly compare the packed value.
                                  // If the address is zero, packed will be zero.
                                  while (packed == 0) {
                                      packed = _packedOwnerships[--curr];
                                  }
                                  return packed;
                              }
                          }
                      }
                  }
                  revert OwnerQueryForNonexistentToken();
              }
              /**
               * @dev Returns the unpacked `TokenOwnership` struct from `packed`.
               */
              function _unpackedOwnership(uint256 packed)
                  private
                  pure
                  returns (TokenOwnership memory ownership)
              {
                  ownership.addr = address(uint160(packed));
                  ownership.startTimestamp = uint64(packed >> _BITPOS_START_TIMESTAMP);
                  ownership.burned = packed & _BITMASK_BURNED != 0;
                  ownership.extraData = uint24(packed >> _BITPOS_EXTRA_DATA);
              }
              /**
               * @dev Packs ownership data into a single uint256.
               */
              function _packOwnershipData(address owner, uint256 flags)
                  private
                  view
                  returns (uint256 result)
              {
                  assembly {
                      // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
                      owner := and(owner, _BITMASK_ADDRESS)
                      // `owner | (block.timestamp << _BITPOS_START_TIMESTAMP) | flags`.
                      result := or(
                          owner,
                          or(shl(_BITPOS_START_TIMESTAMP, timestamp()), flags)
                      )
                  }
              }
              /**
               * @dev Returns the `nextInitialized` flag set if `quantity` equals 1.
               */
              function _nextInitializedFlag(uint256 quantity)
                  private
                  pure
                  returns (uint256 result)
              {
                  // For branchless setting of the `nextInitialized` flag.
                  assembly {
                      // `(quantity == 1) << _BITPOS_NEXT_INITIALIZED`.
                      result := shl(_BITPOS_NEXT_INITIALIZED, eq(quantity, 1))
                  }
              }
              // =============================================================
              //                      APPROVAL OPERATIONS
              // =============================================================
              /**
               * @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) public virtual override {
                  address owner = ownerOf(tokenId);
                  if (_msgSenderERC721A() != owner) {
                      if (!isApprovedForAll(owner, _msgSenderERC721A())) {
                          revert ApprovalCallerNotOwnerNorApproved();
                      }
                  }
                  _tokenApprovals[tokenId].value = to;
                  emit Approval(owner, to, tokenId);
              }
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId)
                  public
                  view
                  virtual
                  override
                  returns (address)
              {
                  if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken();
                  return _tokenApprovals[tokenId].value;
              }
              /**
               * @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)
                  public
                  virtual
                  override
              {
                  _operatorApprovals[_msgSenderERC721A()][operator] = approved;
                  emit ApprovalForAll(_msgSenderERC721A(), operator, approved);
              }
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}.
               */
              function isApprovedForAll(address owner, address operator)
                  public
                  view
                  virtual
                  override
                  returns (bool)
              {
                  return _operatorApprovals[owner][operator];
              }
              /**
               * @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. See {_mint}.
               */
              function _exists(uint256 tokenId) internal view virtual returns (bool) {
                  return
                      _startTokenId() <= tokenId &&
                      tokenId < _currentIndex && // If within bounds,
                      _packedOwnerships[tokenId] & _BITMASK_BURNED == 0; // and not burned.
              }
              /**
               * @dev Returns whether `msgSender` is equal to `approvedAddress` or `owner`.
               */
              function _isSenderApprovedOrOwner(
                  address approvedAddress,
                  address owner,
                  address msgSender
              ) private pure returns (bool result) {
                  assembly {
                      // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
                      owner := and(owner, _BITMASK_ADDRESS)
                      // Mask `msgSender` to the lower 160 bits, in case the upper bits somehow aren't clean.
                      msgSender := and(msgSender, _BITMASK_ADDRESS)
                      // `msgSender == owner || msgSender == approvedAddress`.
                      result := or(eq(msgSender, owner), eq(msgSender, approvedAddress))
                  }
              }
              /**
               * @dev Returns the storage slot and value for the approved address of `tokenId`.
               */
              function _getApprovedSlotAndAddress(uint256 tokenId)
                  private
                  view
                  returns (uint256 approvedAddressSlot, address approvedAddress)
              {
                  TokenApprovalRef storage tokenApproval = _tokenApprovals[tokenId];
                  // The following is equivalent to `approvedAddress = _tokenApprovals[tokenId].value`.
                  assembly {
                      approvedAddressSlot := tokenApproval.slot
                      approvedAddress := sload(approvedAddressSlot)
                  }
              }
              // =============================================================
              //                      TRANSFER OPERATIONS
              // =============================================================
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *
               * 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
              ) public virtual override {
                  uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
                  if (address(uint160(prevOwnershipPacked)) != from)
                      revert TransferFromIncorrectOwner();
                  (
                      uint256 approvedAddressSlot,
                      address approvedAddress
                  ) = _getApprovedSlotAndAddress(tokenId);
                  // The nested ifs save around 20+ gas over a compound boolean condition.
                  if (
                      !_isSenderApprovedOrOwner(
                          approvedAddress,
                          from,
                          _msgSenderERC721A()
                      )
                  ) {
                      if (!isApprovedForAll(from, _msgSenderERC721A()))
                          revert TransferCallerNotOwnerNorApproved();
                  }
                  if (to == address(0)) revert TransferToZeroAddress();
                  _beforeTokenTransfers(from, to, tokenId, 1);
                  // Clear approvals from the previous owner.
                  assembly {
                      if approvedAddress {
                          // This is equivalent to `delete _tokenApprovals[tokenId]`.
                          sstore(approvedAddressSlot, 0)
                      }
                  }
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
                  unchecked {
                      // We can directly increment and decrement the balances.
                      --_packedAddressData[from]; // Updates: `balance -= 1`.
                      ++_packedAddressData[to]; // Updates: `balance += 1`.
                      // Updates:
                      // - `address` to the next owner.
                      // - `startTimestamp` to the timestamp of transfering.
                      // - `burned` to `false`.
                      // - `nextInitialized` to `true`.
                      _packedOwnerships[tokenId] = _packOwnershipData(
                          to,
                          _BITMASK_NEXT_INITIALIZED |
                              _nextExtraData(from, to, prevOwnershipPacked)
                      );
                      // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
                      if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
                          uint256 nextTokenId = tokenId + 1;
                          // If the next slot's address is zero and not burned (i.e. packed value is zero).
                          if (_packedOwnerships[nextTokenId] == 0) {
                              // If the next slot is within bounds.
                              if (nextTokenId != _currentIndex) {
                                  // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                                  _packedOwnerships[nextTokenId] = prevOwnershipPacked;
                              }
                          }
                      }
                  }
                  emit Transfer(from, to, tokenId);
                  _afterTokenTransfers(from, to, tokenId, 1);
              }
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  safeTransferFrom(from, to, tokenId, "");
              }
              /**
               * @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 memory _data
              ) public virtual override {
                  transferFrom(from, to, tokenId);
                  if (to.code.length != 0) {
                      if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
                          revert TransferToNonERC721ReceiverImplementer();
                      }
                  }
              }
              /**
               * @dev Hook that is called before a set of serially-ordered token IDs
               * are about to be transferred. This includes minting.
               * And also called before burning one token.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
               * transferred to `to`.
               * - When `from` is zero, `tokenId` will be minted for `to`.
               * - When `to` is zero, `tokenId` will be burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Hook that is called after a set of serially-ordered token IDs
               * have been transferred. This includes minting.
               * And also called after one token has been burned.
               *
               * `startTokenId` - the first token ID to be transferred.
               * `quantity` - the amount to be transferred.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
               * transferred to `to`.
               * - When `from` is zero, `tokenId` has been minted for `to`.
               * - When `to` is zero, `tokenId` has been burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _afterTokenTransfers(
                  address from,
                  address to,
                  uint256 startTokenId,
                  uint256 quantity
              ) internal virtual {}
              /**
               * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
               *
               * `from` - Previous owner of the given token ID.
               * `to` - Target address that will receive the token.
               * `tokenId` - Token ID to be transferred.
               * `_data` - Optional data to send along with the call.
               *
               * Returns whether the call correctly returned the expected magic value.
               */
              function _checkContractOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory _data
              ) private returns (bool) {
                  try
                      ERC721A__IERC721Receiver(to).onERC721Received(
                          _msgSenderERC721A(),
                          from,
                          tokenId,
                          _data
                      )
                  returns (bytes4 retval) {
                      return
                          retval ==
                          ERC721A__IERC721Receiver(to).onERC721Received.selector;
                  } catch (bytes memory reason) {
                      if (reason.length == 0) {
                          revert TransferToNonERC721ReceiverImplementer();
                      } else {
                          assembly {
                              revert(add(32, reason), mload(reason))
                          }
                      }
                  }
              }
              // =============================================================
              //                        MINT OPERATIONS
              // =============================================================
              /**
               * @dev Mints `quantity` tokens and transfers them to `to`.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `quantity` must be greater than 0.
               *
               * Emits a {Transfer} event for each mint.
               */
              function _mint(address to, uint256 quantity) internal virtual {
                  uint256 startTokenId = _currentIndex;
                  if (quantity == 0) revert MintZeroQuantity();
                  _beforeTokenTransfers(address(0), to, startTokenId, quantity);
                  // Overflows are incredibly unrealistic.
                  // `balance` and `numberMinted` have a maximum limit of 2**64.
                  // `tokenId` has a maximum limit of 2**256.
                  unchecked {
                      // Updates:
                      // - `balance += quantity`.
                      // - `numberMinted += quantity`.
                      //
                      // We can directly add to the `balance` and `numberMinted`.
                      _packedAddressData[to] +=
                          quantity *
                          ((1 << _BITPOS_NUMBER_MINTED) | 1);
                      // Updates:
                      // - `address` to the owner.
                      // - `startTimestamp` to the timestamp of minting.
                      // - `burned` to `false`.
                      // - `nextInitialized` to `quantity == 1`.
                      _packedOwnerships[startTokenId] = _packOwnershipData(
                          to,
                          _nextInitializedFlag(quantity) |
                              _nextExtraData(address(0), to, 0)
                      );
                      uint256 toMasked;
                      uint256 end = startTokenId + quantity;
                      // Use assembly to loop and emit the `Transfer` event for gas savings.
                      // The duplicated `log4` removes an extra check and reduces stack juggling.
                      // The assembly, together with the surrounding Solidity code, have been
                      // delicately arranged to nudge the compiler into producing optimized opcodes.
                      assembly {
                          // Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
                          toMasked := and(to, _BITMASK_ADDRESS)
                          // Emit the `Transfer` event.
                          log4(
                              0, // Start of data (0, since no data).
                              0, // End of data (0, since no data).
                              _TRANSFER_EVENT_SIGNATURE, // Signature.
                              0, // `address(0)`.
                              toMasked, // `to`.
                              startTokenId // `tokenId`.
                          )
                          // The `iszero(eq(,))` check ensures that large values of `quantity`
                          // that overflows uint256 will make the loop run out of gas.
                          // The compiler will optimize the `iszero` away for performance.
                          for {
                              let tokenId := add(startTokenId, 1)
                          } iszero(eq(tokenId, end)) {
                              tokenId := add(tokenId, 1)
                          } {
                              // Emit the `Transfer` event. Similar to above.
                              log4(0, 0, _TRANSFER_EVENT_SIGNATURE, 0, toMasked, tokenId)
                          }
                      }
                      if (toMasked == 0) revert MintToZeroAddress();
                      _currentIndex = end;
                  }
                  _afterTokenTransfers(address(0), to, startTokenId, quantity);
              }
              /**
               * @dev Mints `quantity` tokens and transfers them to `to`.
               *
               * This function is intended for efficient minting only during contract creation.
               *
               * It emits only one {ConsecutiveTransfer} as defined in
               * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309),
               * instead of a sequence of {Transfer} event(s).
               *
               * Calling this function outside of contract creation WILL make your contract
               * non-compliant with the ERC721 standard.
               * For full ERC721 compliance, substituting ERC721 {Transfer} event(s) with the ERC2309
               * {ConsecutiveTransfer} event is only permissible during contract creation.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `quantity` must be greater than 0.
               *
               * Emits a {ConsecutiveTransfer} event.
               */
              function _mintERC2309(address to, uint256 quantity) internal virtual {
                  uint256 startTokenId = _currentIndex;
                  if (to == address(0)) revert MintToZeroAddress();
                  if (quantity == 0) revert MintZeroQuantity();
                  if (quantity > _MAX_MINT_ERC2309_QUANTITY_LIMIT)
                      revert MintERC2309QuantityExceedsLimit();
                  _beforeTokenTransfers(address(0), to, startTokenId, quantity);
                  // Overflows are unrealistic due to the above check for `quantity` to be below the limit.
                  unchecked {
                      // Updates:
                      // - `balance += quantity`.
                      // - `numberMinted += quantity`.
                      //
                      // We can directly add to the `balance` and `numberMinted`.
                      _packedAddressData[to] +=
                          quantity *
                          ((1 << _BITPOS_NUMBER_MINTED) | 1);
                      // Updates:
                      // - `address` to the owner.
                      // - `startTimestamp` to the timestamp of minting.
                      // - `burned` to `false`.
                      // - `nextInitialized` to `quantity == 1`.
                      _packedOwnerships[startTokenId] = _packOwnershipData(
                          to,
                          _nextInitializedFlag(quantity) |
                              _nextExtraData(address(0), to, 0)
                      );
                      emit ConsecutiveTransfer(
                          startTokenId,
                          startTokenId + quantity - 1,
                          address(0),
                          to
                      );
                      _currentIndex = startTokenId + quantity;
                  }
                  _afterTokenTransfers(address(0), to, startTokenId, quantity);
              }
              /**
               * @dev Safely mints `quantity` tokens and transfers them to `to`.
               *
               * Requirements:
               *
               * - If `to` refers to a smart contract, it must implement
               * {IERC721Receiver-onERC721Received}, which is called for each safe transfer.
               * - `quantity` must be greater than 0.
               *
               * See {_mint}.
               *
               * Emits a {Transfer} event for each mint.
               */
              function _safeMint(
                  address to,
                  uint256 quantity,
                  bytes memory _data
              ) internal virtual {
                  _mint(to, quantity);
                  unchecked {
                      if (to.code.length != 0) {
                          uint256 end = _currentIndex;
                          uint256 index = end - quantity;
                          do {
                              if (
                                  !_checkContractOnERC721Received(
                                      address(0),
                                      to,
                                      index++,
                                      _data
                                  )
                              ) {
                                  revert TransferToNonERC721ReceiverImplementer();
                              }
                          } while (index < end);
                          // Reentrancy protection.
                          if (_currentIndex != end) revert();
                      }
                  }
              }
              /**
               * @dev Equivalent to `_safeMint(to, quantity, '')`.
               */
              function _safeMint(address to, uint256 quantity) internal virtual {
                  _safeMint(to, quantity, "");
              }
              // =============================================================
              //                        BURN OPERATIONS
              // =============================================================
              /**
               * @dev Equivalent to `_burn(tokenId, false)`.
               */
              function _burn(uint256 tokenId) internal virtual {
                  _burn(tokenId, false);
              }
              /**
               * @dev Destroys `tokenId`.
               * The approval is cleared when the token is burned.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               *
               * Emits a {Transfer} event.
               */
              function _burn(uint256 tokenId, bool approvalCheck) internal virtual {
                  uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);
                  address from = address(uint160(prevOwnershipPacked));
                  (
                      uint256 approvedAddressSlot,
                      address approvedAddress
                  ) = _getApprovedSlotAndAddress(tokenId);
                  if (approvalCheck) {
                      // The nested ifs save around 20+ gas over a compound boolean condition.
                      if (
                          !_isSenderApprovedOrOwner(
                              approvedAddress,
                              from,
                              _msgSenderERC721A()
                          )
                      ) {
                          if (!isApprovedForAll(from, _msgSenderERC721A()))
                              revert TransferCallerNotOwnerNorApproved();
                      }
                  }
                  _beforeTokenTransfers(from, address(0), tokenId, 1);
                  // Clear approvals from the previous owner.
                  assembly {
                      if approvedAddress {
                          // This is equivalent to `delete _tokenApprovals[tokenId]`.
                          sstore(approvedAddressSlot, 0)
                      }
                  }
                  // Underflow of the sender's balance is impossible because we check for
                  // ownership above and the recipient's balance can't realistically overflow.
                  // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
                  unchecked {
                      // Updates:
                      // - `balance -= 1`.
                      // - `numberBurned += 1`.
                      //
                      // We can directly decrement the balance, and increment the number burned.
                      // This is equivalent to `packed -= 1; packed += 1 << _BITPOS_NUMBER_BURNED;`.
                      _packedAddressData[from] += (1 << _BITPOS_NUMBER_BURNED) - 1;
                      // Updates:
                      // - `address` to the last owner.
                      // - `startTimestamp` to the timestamp of burning.
                      // - `burned` to `true`.
                      // - `nextInitialized` to `true`.
                      _packedOwnerships[tokenId] = _packOwnershipData(
                          from,
                          (_BITMASK_BURNED | _BITMASK_NEXT_INITIALIZED) |
                              _nextExtraData(from, address(0), prevOwnershipPacked)
                      );
                      // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
                      if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
                          uint256 nextTokenId = tokenId + 1;
                          // If the next slot's address is zero and not burned (i.e. packed value is zero).
                          if (_packedOwnerships[nextTokenId] == 0) {
                              // If the next slot is within bounds.
                              if (nextTokenId != _currentIndex) {
                                  // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                                  _packedOwnerships[nextTokenId] = prevOwnershipPacked;
                              }
                          }
                      }
                  }
                  emit Transfer(from, address(0), tokenId);
                  _afterTokenTransfers(from, address(0), tokenId, 1);
                  // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
                  unchecked {
                      _burnCounter++;
                  }
              }
              // =============================================================
              //                     EXTRA DATA OPERATIONS
              // =============================================================
              /**
               * @dev Directly sets the extra data for the ownership data `index`.
               */
              function _setExtraDataAt(uint256 index, uint24 extraData) internal virtual {
                  uint256 packed = _packedOwnerships[index];
                  if (packed == 0) revert OwnershipNotInitializedForExtraData();
                  uint256 extraDataCasted;
                  // Cast `extraData` with assembly to avoid redundant masking.
                  assembly {
                      extraDataCasted := extraData
                  }
                  packed =
                      (packed & _BITMASK_EXTRA_DATA_COMPLEMENT) |
                      (extraDataCasted << _BITPOS_EXTRA_DATA);
                  _packedOwnerships[index] = packed;
              }
              /**
               * @dev Called during each token transfer to set the 24bit `extraData` field.
               * Intended to be overridden by the cosumer contract.
               *
               * `previousExtraData` - the value of `extraData` before transfer.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
               * transferred to `to`.
               * - When `from` is zero, `tokenId` will be minted for `to`.
               * - When `to` is zero, `tokenId` will be burned by `from`.
               * - `from` and `to` are never both zero.
               */
              function _extraData(
                  address from,
                  address to,
                  uint24 previousExtraData
              ) internal view virtual returns (uint24) {}
              /**
               * @dev Returns the next extra data for the packed ownership data.
               * The returned result is shifted into position.
               */
              function _nextExtraData(
                  address from,
                  address to,
                  uint256 prevOwnershipPacked
              ) private view returns (uint256) {
                  uint24 extraData = uint24(prevOwnershipPacked >> _BITPOS_EXTRA_DATA);
                  return uint256(_extraData(from, to, extraData)) << _BITPOS_EXTRA_DATA;
              }
              // =============================================================
              //                       OTHER OPERATIONS
              // =============================================================
              /**
               * @dev Returns the message sender (defaults to `msg.sender`).
               *
               * If you are writing GSN compatible contracts, you need to override this function.
               */
              function _msgSenderERC721A() internal view virtual returns (address) {
                  return msg.sender;
              }
              /**
               * @dev Converts a uint256 to its ASCII string decimal representation.
               */
              function _toString(uint256 value)
                  internal
                  pure
                  virtual
                  returns (string memory str)
              {
                  assembly {
                      // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
                      // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
                      // We will need 1 word for the trailing zeros padding, 1 word for the length,
                      // and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
                      let m := add(mload(0x40), 0xa0)
                      // Update the free memory pointer to allocate.
                      mstore(0x40, m)
                      // Assign the `str` to the end.
                      str := sub(m, 0x20)
                      // Zeroize the slot after the string.
                      mstore(str, 0)
                      // Cache the end of the memory to calculate the length later.
                      let end := str
                      // We write the string from rightmost digit to leftmost digit.
                      // The following is essentially a do-while loop that also handles the zero case.
                      // prettier-ignore
                      for { let temp := value } 1 {} {
                          str := sub(str, 1)
                          // Write the character to the pointer.
                          // The ASCII index of the '0' character is 48.
                          mstore8(str, add(48, mod(temp, 10)))
                          // Keep dividing `temp` until zero.
                          temp := div(temp, 10)
                          // prettier-ignore
                          if iszero(temp) { break }
                      }
                      let length := sub(end, str)
                      // Move the pointer 32 bytes leftwards to make room for the length.
                      str := sub(str, 0x20)
                      // Store the length.
                      mstore(str, length)
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuardUpgradeable is Initializable {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              function __ReentrancyGuard_init() internal onlyInitializing {
                  __ReentrancyGuard_init_unchained();
              }
              function __ReentrancyGuard_init_unchained() internal onlyInitializing {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  _nonReentrantBefore();
                  _;
                  _nonReentrantAfter();
              }
              function _nonReentrantBefore() private {
                  // On the first call to nonReentrant, _status will be _NOT_ENTERED
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
              }
              function _nonReentrantAfter() private {
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
               * `nonReentrant` function in the call stack.
               */
              function _reentrancyGuardEntered() internal view returns (bool) {
                  return _status == _ENTERED;
              }
              /**
               * @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 (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
          pragma solidity 0.8.17;
          import { IERC2981 } from "openzeppelin-contracts/interfaces/IERC2981.sol";
          interface ISeaDropTokenContractMetadata is IERC2981 {
              /**
               * @notice Throw if the max supply exceeds uint64, a limit
               *         due to the storage of bit-packed variables in ERC721A.
               */
              error CannotExceedMaxSupplyOfUint64(uint256 newMaxSupply);
              /**
               * @dev Revert with an error when attempting to set the provenance
               *      hash after the mint has started.
               */
              error ProvenanceHashCannotBeSetAfterMintStarted();
              /**
               * @dev Revert if the royalty basis points is greater than 10_000.
               */
              error InvalidRoyaltyBasisPoints(uint256 basisPoints);
              /**
               * @dev Revert if the royalty address is being set to the zero address.
               */
              error RoyaltyAddressCannotBeZeroAddress();
              /**
               * @dev Emit an event for token metadata reveals/updates,
               *      according to EIP-4906.
               *
               * @param _fromTokenId The start token id.
               * @param _toTokenId   The end token id.
               */
              event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
              /**
               * @dev Emit an event when the URI for the collection-level metadata
               *      is updated.
               */
              event ContractURIUpdated(string newContractURI);
              /**
               * @dev Emit an event when the max token supply is updated.
               */
              event MaxSupplyUpdated(uint256 newMaxSupply);
              /**
               * @dev Emit an event with the previous and new provenance hash after
               *      being updated.
               */
              event ProvenanceHashUpdated(bytes32 previousHash, bytes32 newHash);
              /**
               * @dev Emit an event when the royalties info is updated.
               */
              event RoyaltyInfoUpdated(address receiver, uint256 bps);
              /**
               * @notice A struct defining royalty info for the contract.
               */
              struct RoyaltyInfo {
                  address royaltyAddress;
                  uint96 royaltyBps;
              }
              /**
               * @notice Sets the base URI for the token metadata and emits an event.
               *
               * @param tokenURI The new base URI to set.
               */
              function setBaseURI(string calldata tokenURI) external;
              /**
               * @notice Sets the contract URI for contract metadata.
               *
               * @param newContractURI The new contract URI.
               */
              function setContractURI(string calldata newContractURI) external;
              /**
               * @notice Sets the max supply and emits an event.
               *
               * @param newMaxSupply The new max supply to set.
               */
              function setMaxSupply(uint256 newMaxSupply) external;
              /**
               * @notice Sets the provenance hash and emits an event.
               *
               *         The provenance hash is used for random reveals, which
               *         is a hash of the ordered metadata to show it has not been
               *         modified after mint started.
               *
               *         This function will revert after the first item has been minted.
               *
               * @param newProvenanceHash The new provenance hash to set.
               */
              function setProvenanceHash(bytes32 newProvenanceHash) external;
              /**
               * @notice Sets the address and basis points for royalties.
               *
               * @param newInfo The struct to configure royalties.
               */
              function setRoyaltyInfo(RoyaltyInfo calldata newInfo) external;
              /**
               * @notice Returns the base URI for token metadata.
               */
              function baseURI() external view returns (string memory);
              /**
               * @notice Returns the contract URI.
               */
              function contractURI() external view returns (string memory);
              /**
               * @notice Returns the max token supply.
               */
              function maxSupply() external view returns (uint256);
              /**
               * @notice Returns the provenance hash.
               *         The provenance hash is used for random reveals, which
               *         is a hash of the ordered metadata to show it is unmodified
               *         after mint has started.
               */
              function provenanceHash() external view returns (bytes32);
              /**
               * @notice Returns the address that receives royalties.
               */
              function royaltyAddress() external view returns (address);
              /**
               * @notice Returns the royalty basis points out of 10_000.
               */
              function royaltyBasisPoints() external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import { ERC721ACloneable } from "./ERC721ACloneable.sol";
          /**
           * @title  ERC721AConduitPreapprovedCloneable
           * @notice ERC721A with the OpenSea conduit preapproved.
           */
          abstract contract ERC721AConduitPreapprovedCloneable is ERC721ACloneable {
              /// @dev The canonical OpenSea conduit.
              address internal constant _CONDUIT =
                  0x1E0049783F008A0085193E00003D00cd54003c71;
              /**
               * @dev Returns if the `operator` is allowed to manage all of the
               *      assets of `owner`. Always returns true for the conduit.
               */
              function isApprovedForAll(address owner, address operator)
                  public
                  view
                  virtual
                  override
                  returns (bool)
              {
                  if (operator == _CONDUIT) {
                      return true;
                  }
                  return ERC721ACloneable.isApprovedForAll(owner, operator);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          import { ICreatorToken } from "../interfaces/ICreatorToken.sol";
          /**
           * @title  ERC721TransferValidator
           * @notice Functionality to use a transfer validator.
           */
          abstract contract ERC721TransferValidator is ICreatorToken {
              /// @dev Store the transfer validator. The null address means no transfer validator is set.
              address internal _transferValidator;
              /// @notice Revert with an error if the transfer validator is being set to the same address.
              error SameTransferValidator();
              /// @notice Returns the currently active transfer validator.
              ///         The null address means no transfer validator is set.
              function getTransferValidator() external view returns (address) {
                  return _transferValidator;
              }
              /// @notice Set the transfer validator.
              ///         The external method that uses this must include access control.
              function _setTransferValidator(address newValidator) internal {
                  address oldValidator = _transferValidator;
                  if (oldValidator == newValidator) {
                      revert SameTransferValidator();
                  }
                  _transferValidator = newValidator;
                  emit TransferValidatorUpdated(oldValidator, newValidator);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          interface ICreatorToken {
              event TransferValidatorUpdated(address oldValidator, address newValidator);
              function getTransferValidator() external view returns (address validator);
              function getTransferValidationFunction()
                  external
                  view
                  returns (bytes4 functionSignature, bool isViewFunction);
              function setTransferValidator(address validator) external;
          }
          interface ILegacyCreatorToken {
              event TransferValidatorUpdated(address oldValidator, address newValidator);
              function getTransferValidator() external view returns (address validator);
              function setTransferValidator(address validator) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.17;
          interface ITransferValidator721 {
              /// @notice Ensure that a transfer has been authorized for a specific tokenId
              function validateTransfer(
                  address caller,
                  address from,
                  address to,
                  uint256 tokenId
              ) external view;
          }
          interface ITransferValidator1155 {
              /// @notice Ensure that a transfer has been authorized for a specific amount of a specific tokenId, and reduce the transferable amount remaining
              function validateTransfer(
                  address caller,
                  address from,
                  address to,
                  uint256 tokenId,
                  uint256 amount
              ) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.4;
          import {ConstructorInitializable} from "./ConstructorInitializable.sol";
          /**
          @notice A two-step extension of Ownable, where the new owner must claim ownership of the contract after owner initiates transfer
          Owner can cancel the transfer at any point before the new owner claims ownership.
          Helpful in guarding against transferring ownership to an address that is unable to act as the Owner.
          */
          abstract contract TwoStepOwnable is ConstructorInitializable {
              address private _owner;
              event OwnershipTransferred(
                  address indexed previousOwner,
                  address indexed newOwner
              );
              address internal potentialOwner;
              event PotentialOwnerUpdated(address newPotentialAdministrator);
              error NewOwnerIsZeroAddress();
              error NotNextOwner();
              error OnlyOwner();
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              constructor() {
                  _initialize();
              }
              function _initialize() private onlyConstructor {
                  _transferOwnership(msg.sender);
              }
              ///@notice Initiate ownership transfer to newPotentialOwner. Note: new owner will have to manually acceptOwnership
              ///@param newPotentialOwner address of potential new owner
              function transferOwnership(address newPotentialOwner)
                  public
                  virtual
                  onlyOwner
              {
                  if (newPotentialOwner == address(0)) {
                      revert NewOwnerIsZeroAddress();
                  }
                  potentialOwner = newPotentialOwner;
                  emit PotentialOwnerUpdated(newPotentialOwner);
              }
              ///@notice Claim ownership of smart contract, after the current owner has initiated the process with transferOwnership
              function acceptOwnership() public virtual {
                  address _potentialOwner = potentialOwner;
                  if (msg.sender != _potentialOwner) {
                      revert NotNextOwner();
                  }
                  delete potentialOwner;
                  emit PotentialOwnerUpdated(address(0));
                  _transferOwnership(_potentialOwner);
              }
              ///@notice cancel ownership transfer
              function cancelOwnershipTransfer() public virtual onlyOwner {
                  delete potentialOwner;
                  emit PotentialOwnerUpdated(address(0));
              }
              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 != msg.sender) {
                      revert OnlyOwner();
                  }
              }
              /**
               * @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`).
               * 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.6.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
          pragma solidity 0.8.17;
          import { PublicDrop, TokenGatedDropStage, SignedMintValidationParams } from "./SeaDropStructs.sol";
          interface SeaDropErrorsAndEvents {
              /**
               * @dev Revert with an error if the drop stage is not active.
               */
              error NotActive(
                  uint256 currentTimestamp,
                  uint256 startTimestamp,
                  uint256 endTimestamp
              );
              /**
               * @dev Revert with an error if the mint quantity is zero.
               */
              error MintQuantityCannotBeZero();
              /**
               * @dev Revert with an error if the mint quantity exceeds the max allowed
               *      to be minted per wallet.
               */
              error MintQuantityExceedsMaxMintedPerWallet(uint256 total, uint256 allowed);
              /**
               * @dev Revert with an error if the mint quantity exceeds the max token
               *      supply.
               */
              error MintQuantityExceedsMaxSupply(uint256 total, uint256 maxSupply);
              /**
               * @dev Revert with an error if the mint quantity exceeds the max token
               *      supply for the stage.
               *      Note: The `maxTokenSupplyForStage` for public mint is
               *      always `type(uint).max`.
               */
              error MintQuantityExceedsMaxTokenSupplyForStage(
                  uint256 total, 
                  uint256 maxTokenSupplyForStage
              );
              
              /**
               * @dev Revert if the fee recipient is the zero address.
               */
              error FeeRecipientCannotBeZeroAddress();
              /**
               * @dev Revert if the fee recipient is not already included.
               */
              error FeeRecipientNotPresent();
              /**
               * @dev Revert if the fee basis points is greater than 10_000.
               */
              error InvalidFeeBps(uint256 feeBps);
              /**
               * @dev Revert if the fee recipient is already included.
               */
              error DuplicateFeeRecipient();
              /**
               * @dev Revert if the fee recipient is restricted and not allowed.
               */
              error FeeRecipientNotAllowed();
              /**
               * @dev Revert if the creator payout address is the zero address.
               */
              error CreatorPayoutAddressCannotBeZeroAddress();
              /**
               * @dev Revert with an error if the received payment is incorrect.
               */
              error IncorrectPayment(uint256 got, uint256 want);
              /**
               * @dev Revert with an error if the allow list proof is invalid.
               */
              error InvalidProof();
              /**
               * @dev Revert if a supplied signer address is the zero address.
               */
              error SignerCannotBeZeroAddress();
              /**
               * @dev Revert with an error if signer's signature is invalid.
               */
              error InvalidSignature(address recoveredSigner);
              /**
               * @dev Revert with an error if a signer is not included in
               *      the enumeration when removing.
               */
              error SignerNotPresent();
              /**
               * @dev Revert with an error if a payer is not included in
               *      the enumeration when removing.
               */
              error PayerNotPresent();
              /**
               * @dev Revert with an error if a payer is already included in mapping
               *      when adding.
               *      Note: only applies when adding a single payer, as duplicates in
               *      enumeration can be removed with updatePayer.
               */
              error DuplicatePayer();
              /**
               * @dev Revert with an error if the payer is not allowed. The minter must
               *      pay for their own mint.
               */
              error PayerNotAllowed();
              /**
               * @dev Revert if a supplied payer address is the zero address.
               */
              error PayerCannotBeZeroAddress();
              /**
               * @dev Revert with an error if the sender does not
               *      match the INonFungibleSeaDropToken interface.
               */
              error OnlyINonFungibleSeaDropToken(address sender);
              /**
               * @dev Revert with an error if the sender of a token gated supplied
               *      drop stage redeem is not the owner of the token.
               */
              error TokenGatedNotTokenOwner(
                  address nftContract,
                  address allowedNftToken,
                  uint256 allowedNftTokenId
              );
              /**
               * @dev Revert with an error if the token id has already been used to
               *      redeem a token gated drop stage.
               */
              error TokenGatedTokenIdAlreadyRedeemed(
                  address nftContract,
                  address allowedNftToken,
                  uint256 allowedNftTokenId
              );
              /**
               * @dev Revert with an error if an empty TokenGatedDropStage is provided
               *      for an already-empty TokenGatedDropStage.
               */
               error TokenGatedDropStageNotPresent();
              /**
               * @dev Revert with an error if an allowedNftToken is set to
               *      the zero address.
               */
               error TokenGatedDropAllowedNftTokenCannotBeZeroAddress();
              /**
               * @dev Revert with an error if an allowedNftToken is set to
               *      the drop token itself.
               */
               error TokenGatedDropAllowedNftTokenCannotBeDropToken();
              /**
               * @dev Revert with an error if supplied signed mint price is less than
               *      the minimum specified.
               */
              error InvalidSignedMintPrice(uint256 got, uint256 minimum);
              /**
               * @dev Revert with an error if supplied signed maxTotalMintableByWallet
               *      is greater than the maximum specified.
               */
              error InvalidSignedMaxTotalMintableByWallet(uint256 got, uint256 maximum);
              /**
               * @dev Revert with an error if supplied signed start time is less than
               *      the minimum specified.
               */
              error InvalidSignedStartTime(uint256 got, uint256 minimum);
              
              /**
               * @dev Revert with an error if supplied signed end time is greater than
               *      the maximum specified.
               */
              error InvalidSignedEndTime(uint256 got, uint256 maximum);
              /**
               * @dev Revert with an error if supplied signed maxTokenSupplyForStage
               *      is greater than the maximum specified.
               */
               error InvalidSignedMaxTokenSupplyForStage(uint256 got, uint256 maximum);
              
               /**
               * @dev Revert with an error if supplied signed feeBps is greater than
               *      the maximum specified, or less than the minimum.
               */
              error InvalidSignedFeeBps(uint256 got, uint256 minimumOrMaximum);
              /**
               * @dev Revert with an error if signed mint did not specify to restrict
               *      fee recipients.
               */
              error SignedMintsMustRestrictFeeRecipients();
              /**
               * @dev Revert with an error if a signature for a signed mint has already
               *      been used.
               */
              error SignatureAlreadyUsed();
              /**
               * @dev An event with details of a SeaDrop mint, for analytical purposes.
               * 
               * @param nftContract    The nft contract.
               * @param minter         The mint recipient.
               * @param feeRecipient   The fee recipient.
               * @param payer          The address who payed for the tx.
               * @param quantityMinted The number of tokens minted.
               * @param unitMintPrice  The amount paid for each token.
               * @param feeBps         The fee out of 10_000 basis points collected.
               * @param dropStageIndex The drop stage index. Items minted
               *                       through mintPublic() have
               *                       dropStageIndex of 0.
               */
              event SeaDropMint(
                  address indexed nftContract,
                  address indexed minter,
                  address indexed feeRecipient,
                  address payer,
                  uint256 quantityMinted,
                  uint256 unitMintPrice,
                  uint256 feeBps,
                  uint256 dropStageIndex
              );
              /**
               * @dev An event with updated public drop data for an nft contract.
               */
              event PublicDropUpdated(
                  address indexed nftContract,
                  PublicDrop publicDrop
              );
              /**
               * @dev An event with updated token gated drop stage data
               *      for an nft contract.
               */
              event TokenGatedDropStageUpdated(
                  address indexed nftContract,
                  address indexed allowedNftToken,
                  TokenGatedDropStage dropStage
              );
              /**
               * @dev An event with updated allow list data for an nft contract.
               * 
               * @param nftContract        The nft contract.
               * @param previousMerkleRoot The previous allow list merkle root.
               * @param newMerkleRoot      The new allow list merkle root.
               * @param publicKeyURI       If the allow list is encrypted, the public key
               *                           URIs that can decrypt the list.
               *                           Empty if unencrypted.
               * @param allowListURI       The URI for the allow list.
               */
              event AllowListUpdated(
                  address indexed nftContract,
                  bytes32 indexed previousMerkleRoot,
                  bytes32 indexed newMerkleRoot,
                  string[] publicKeyURI,
                  string allowListURI
              );
              /**
               * @dev An event with updated drop URI for an nft contract.
               */
              event DropURIUpdated(address indexed nftContract, string newDropURI);
              /**
               * @dev An event with the updated creator payout address for an nft
               *      contract.
               */
              event CreatorPayoutAddressUpdated(
                  address indexed nftContract,
                  address indexed newPayoutAddress
              );
              /**
               * @dev An event with the updated allowed fee recipient for an nft
               *      contract.
               */
              event AllowedFeeRecipientUpdated(
                  address indexed nftContract,
                  address indexed feeRecipient,
                  bool indexed allowed
              );
              /**
               * @dev An event with the updated validation parameters for server-side
               *      signers.
               */
              event SignedMintValidationParamsUpdated(
                  address indexed nftContract,
                  address indexed signer,
                  SignedMintValidationParams signedMintValidationParams
              );   
              /**
               * @dev An event with the updated payer for an nft contract.
               */
              event PayerUpdated(
                  address indexed nftContract,
                  address indexed payer,
                  bool indexed allowed
              );
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.2
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          /**
           * @dev Interface of ERC721A.
           */
          interface IERC721A {
              /**
               * The caller must own the token or be an approved operator.
               */
              error ApprovalCallerNotOwnerNorApproved();
              /**
               * The token does not exist.
               */
              error ApprovalQueryForNonexistentToken();
              /**
               * Cannot query the balance for the zero address.
               */
              error BalanceQueryForZeroAddress();
              /**
               * Cannot mint to the zero address.
               */
              error MintToZeroAddress();
              /**
               * The quantity of tokens minted must be more than zero.
               */
              error MintZeroQuantity();
              /**
               * The token does not exist.
               */
              error OwnerQueryForNonexistentToken();
              /**
               * The caller must own the token or be an approved operator.
               */
              error TransferCallerNotOwnerNorApproved();
              /**
               * The token must be owned by `from`.
               */
              error TransferFromIncorrectOwner();
              /**
               * Cannot safely transfer to a contract that does not implement the
               * ERC721Receiver interface.
               */
              error TransferToNonERC721ReceiverImplementer();
              /**
               * Cannot transfer to the zero address.
               */
              error TransferToZeroAddress();
              /**
               * The token does not exist.
               */
              error URIQueryForNonexistentToken();
              /**
               * The `quantity` minted with ERC2309 exceeds the safety limit.
               */
              error MintERC2309QuantityExceedsLimit();
              /**
               * The `extraData` cannot be set on an unintialized ownership slot.
               */
              error OwnershipNotInitializedForExtraData();
              // =============================================================
              //                            STRUCTS
              // =============================================================
              struct TokenOwnership {
                  // The address of the owner.
                  address addr;
                  // Stores the start time of ownership with minimal overhead for tokenomics.
                  uint64 startTimestamp;
                  // Whether the token has been burned.
                  bool burned;
                  // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
                  uint24 extraData;
              }
              // =============================================================
              //                         TOKEN COUNTERS
              // =============================================================
              /**
               * @dev Returns the total number of tokens in existence.
               * Burned tokens will reduce the count.
               * To get the total number of tokens minted, please see {_totalMinted}.
               */
              function totalSupply() external view returns (uint256);
              // =============================================================
              //                            IERC165
              // =============================================================
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
              // =============================================================
              //                            IERC721
              // =============================================================
              /**
               * @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`,
               * 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 be 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,
                  bytes calldata data
              ) external;
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
               * whenever possible.
               *
               * 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);
              // =============================================================
              //                        IERC721Metadata
              // =============================================================
              /**
               * @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);
              // =============================================================
              //                           IERC2309
              // =============================================================
              /**
               * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
               * (inclusive) is transferred from `from` to `to`, as defined in the
               * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
               *
               * See {_mintERC2309} for more details.
               */
              event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.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
          pragma solidity >=0.8.4;
          /**
           * @author emo.eth
           * @notice Abstract smart contract that provides an onlyUninitialized modifier which only allows calling when
           *         from within a constructor of some sort, whether directly instantiating an inherting contract,
           *         or when delegatecalling from a proxy
           */
          abstract contract ConstructorInitializable {
              error AlreadyInitialized();
              modifier onlyConstructor() {
                  if (address(this).code.length != 0) {
                      revert AlreadyInitialized();
                  }
                  _;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.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://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.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);
                  }
              }
          }
          

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

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