ETH Price: $2,497.68 (+1.19%)

Transaction Decoder

Block:
20353966 at Jul-21-2024 09:18:11 AM +UTC
Transaction Fee:
0.000240449588118208 ETH $0.60
Gas Used:
96,136 Gas / 2.501139928 Gwei

Emitted Events:

95 DenizensOfZenAcademy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000008b1f33f7b04c563da12f8af6cbf33dd3e1c6203e, 0x00000000000000000000000075c84c7b446cebbb3830e3d64a042599b884c83a, 0x0000000000000000000000000000000000000000000000000000000000000206 )

Account State Difference:

  Address   Before After State Difference Code
0x1Cfb2525...11d419964
0x8b1F33F7...3E1c6203E
1.107453949923543677 Eth
Nonce: 18
1.107213500335425469 Eth
Nonce: 19
0.000240449588118208
(beaverbuild)
13.520189057974769621 Eth13.520219608567757749 Eth0.000030550592988128

Execution Trace

TransferHelper.bulkTransfer( items=, conduitKey=0000007B02230091A7ED01230072F7006A004D60A8D4E71D599B8104250F0000 ) => ( items=, conduitKey= )
  • Conduit.execute( transfers= ) => ( transfers= )
    • DenizensOfZenAcademy.23b872dd( )
      • AddressRelay.getImplAddress( _functionSelector=System.Byte[] ) => ( 0x4674F9Cf8fCe3E9fF332015A0F0859Baa60c2deD )
      • HeyMintERC721ABase.transferFrom( from=0x8b1F33F7B04C563Da12F8Af6CBf33dd3E1c6203E, to=0x75C84C7B446cEBBb3830E3D64A042599b884C83a, tokenId=518 )
        • OperatorFilterRegistry.isOperatorAllowed( registrant=0x1Cfb252575aBbb30262B4f8Ab7519C111d419964, operator=0x1E0049783F008A0085193E00003D00cd54003c71 ) => ( True )
        • OperatorFilterRegistry.isOperatorAllowed( registrant=0x1Cfb252575aBbb30262B4f8Ab7519C111d419964, operator=0x1E0049783F008A0085193E00003D00cd54003c71 ) => ( True )
          bulkTransfer[TransferHelper (ln:57)]
          File 1 of 6: TransferHelper
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import { IERC721Receiver } from "../interfaces/IERC721Receiver.sol";
          import "./TransferHelperStructs.sol";
          import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
          import {
              ConduitControllerInterface
          } from "../interfaces/ConduitControllerInterface.sol";
          import { Conduit } from "../conduit/Conduit.sol";
          import { ConduitTransfer } from "../conduit/lib/ConduitStructs.sol";
          import {
              TransferHelperInterface
          } from "../interfaces/TransferHelperInterface.sol";
          import { TransferHelperErrors } from "../interfaces/TransferHelperErrors.sol";
          /**
           * @title TransferHelper
           * @author stephankmin, stuckinaboot, ryanio
           * @notice TransferHelper is a utility contract for transferring
           *         ERC20/ERC721/ERC1155 items in bulk to specific recipients.
           */
          contract TransferHelper is TransferHelperInterface, TransferHelperErrors {
              // Allow for interaction with the conduit controller.
              ConduitControllerInterface internal immutable _CONDUIT_CONTROLLER;
              // Set conduit creation code and runtime code hashes as immutable arguments.
              bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH;
              bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH;
              /**
               * @dev Set the supplied conduit controller and retrieve its
               *      conduit creation code hash.
               *
               *
               * @param conduitController A contract that deploys conduits, or proxies
               *                          that may optionally be used to transfer approved
               *                          ERC20/721/1155 tokens.
               */
              constructor(address conduitController) {
                  // Get the conduit creation code and runtime code hashes from the
                  // supplied conduit controller and set them as an immutable.
                  ConduitControllerInterface controller = ConduitControllerInterface(
                      conduitController
                  );
                  (_CONDUIT_CREATION_CODE_HASH, _CONDUIT_RUNTIME_CODE_HASH) = controller
                      .getConduitCodeHashes();
                  // Set the supplied conduit controller as an immutable.
                  _CONDUIT_CONTROLLER = controller;
              }
              /**
               * @notice Transfer multiple ERC20/ERC721/ERC1155 items to
               *         specified recipients.
               *
               * @param items      The items to transfer to an intended recipient.
               * @param conduitKey An optional conduit key referring to a conduit through
               *                   which the bulk transfer should occur.
               *
               * @return magicValue A value indicating that the transfers were successful.
               */
              function bulkTransfer(
                  TransferHelperItemsWithRecipient[] calldata items,
                  bytes32 conduitKey
              ) external override returns (bytes4 magicValue) {
                  // Ensure that a conduit key has been supplied.
                  if (conduitKey == bytes32(0)) {
                      revert InvalidConduit(conduitKey, address(0));
                  }
                  // Use conduit derived from supplied conduit key to perform transfers.
                  _performTransfersWithConduit(items, conduitKey);
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.bulkTransfer.selector;
              }
              /**
               * @notice Perform multiple transfers to specified recipients via the
               *         conduit derived from the provided conduit key.
               *
               * @param transfers  The items to transfer.
               * @param conduitKey The conduit key referring to the conduit through
               *                   which the bulk transfer should occur.
               */
              function _performTransfersWithConduit(
                  TransferHelperItemsWithRecipient[] calldata transfers,
                  bytes32 conduitKey
              ) internal {
                  // Retrieve total number of transfers and place on stack.
                  uint256 numTransfers = transfers.length;
                  // Derive the conduit address from the deployer, conduit key
                  // and creation code hash.
                  address conduit = address(
                      uint160(
                          uint256(
                              keccak256(
                                  abi.encodePacked(
                                      bytes1(0xff),
                                      address(_CONDUIT_CONTROLLER),
                                      conduitKey,
                                      _CONDUIT_CREATION_CODE_HASH
                                  )
                              )
                          )
                      )
                  );
                  // Declare a variable to store the sum of all items across transfers.
                  uint256 sumOfItemsAcrossAllTransfers;
                  // Skip overflow checks: all for loops are indexed starting at zero.
                  unchecked {
                      // Iterate over each transfer.
                      for (uint256 i = 0; i < numTransfers; ++i) {
                          // Retrieve the transfer in question.
                          TransferHelperItemsWithRecipient calldata transfer = transfers[
                              i
                          ];
                          // Increment totalItems by the number of items in the transfer.
                          sumOfItemsAcrossAllTransfers += transfer.items.length;
                      }
                  }
                  // Declare a new array in memory with length totalItems to populate with
                  // each conduit transfer.
                  ConduitTransfer[] memory conduitTransfers = new ConduitTransfer[](
                      sumOfItemsAcrossAllTransfers
                  );
                  // Declare an index for storing ConduitTransfers in conduitTransfers.
                  uint256 itemIndex;
                  // Skip overflow checks: all for loops are indexed starting at zero.
                  unchecked {
                      // Iterate over each transfer.
                      for (uint256 i = 0; i < numTransfers; ++i) {
                          // Retrieve the transfer in question.
                          TransferHelperItemsWithRecipient calldata transfer = transfers[
                              i
                          ];
                          // Retrieve the items of the transfer in question.
                          TransferHelperItem[] calldata transferItems = transfer.items;
                          // Ensure recipient is not the zero address.
                          _checkRecipientIsNotZeroAddress(transfer.recipient);
                          // Create a boolean indicating whether validateERC721Receiver
                          // is true and recipient is a contract.
                          bool callERC721Receiver = transfer.validateERC721Receiver &&
                              transfer.recipient.code.length != 0;
                          // Retrieve the total number of items in the transfer and
                          // place on stack.
                          uint256 numItemsInTransfer = transferItems.length;
                          // Iterate over each item in the transfer to create a
                          // corresponding ConduitTransfer.
                          for (uint256 j = 0; j < numItemsInTransfer; ++j) {
                              // Retrieve the item from the transfer.
                              TransferHelperItem calldata item = transferItems[j];
                              if (item.itemType == ConduitItemType.ERC20) {
                                  // Ensure that the identifier of an ERC20 token is 0.
                                  if (item.identifier != 0) {
                                      revert InvalidERC20Identifier();
                                  }
                              }
                              // If the item is an ERC721 token and
                              // callERC721Receiver is true...
                              if (item.itemType == ConduitItemType.ERC721) {
                                  if (callERC721Receiver) {
                                      // Check if the recipient implements
                                      // onERC721Received for the given tokenId.
                                      _checkERC721Receiver(
                                          conduit,
                                          transfer.recipient,
                                          item.identifier
                                      );
                                  }
                              }
                              // Create a ConduitTransfer corresponding to each
                              // TransferHelperItem.
                              conduitTransfers[itemIndex] = ConduitTransfer(
                                  item.itemType,
                                  item.token,
                                  msg.sender,
                                  transfer.recipient,
                                  item.identifier,
                                  item.amount
                              );
                              // Increment the index for storing ConduitTransfers.
                              ++itemIndex;
                          }
                      }
                  }
                  // Attempt the external call to transfer tokens via the derived conduit.
                  try ConduitInterface(conduit).execute(conduitTransfers) returns (
                      bytes4 conduitMagicValue
                  ) {
                      // Check if the value returned from the external call matches
                      // the conduit `execute` selector.
                      if (conduitMagicValue != ConduitInterface.execute.selector) {
                          // If the external call fails, revert with the conduit key
                          // and conduit address.
                          revert InvalidConduit(conduitKey, conduit);
                      }
                  } catch Error(string memory reason) {
                      // Catch reverts with a provided reason string and
                      // revert with the reason, conduit key and conduit address.
                      revert ConduitErrorRevertString(reason, conduitKey, conduit);
                  } catch (bytes memory data) {
                      // Conduits will throw a custom error when attempting to transfer
                      // native token item types or an ERC721 item amount other than 1.
                      // Bubble up these custom errors when encountered. Note that the
                      // conduit itself will bubble up revert reasons from transfers as
                      // well, meaning that these errors are not necessarily indicative of
                      // an issue with the item type or amount in cases where the same
                      // custom error signature is encountered during a conduit transfer.
                      // Set initial value of first four bytes of revert data to the mask.
                      bytes4 customErrorSelector = bytes4(0xffffffff);
                      // Utilize assembly to read first four bytes (if present) directly.
                      assembly {
                          // Combine original mask with first four bytes of revert data.
                          customErrorSelector := and(
                              mload(add(data, 0x20)), // Data begins after length offset.
                              customErrorSelector
                          )
                      }
                      // Pass through the custom error in question if the revert data is
                      // the correct length and matches an expected custom error selector.
                      if (
                          data.length == 4 &&
                          (customErrorSelector == InvalidItemType.selector ||
                              customErrorSelector == InvalidERC721TransferAmount.selector)
                      ) {
                          // "Bubble up" the revert reason.
                          assembly {
                              revert(add(data, 0x20), 0x04)
                          }
                      }
                      // Catch all other reverts from the external call to the conduit and
                      // include the conduit's raw revert reason as a data argument to a
                      // new custom error.
                      revert ConduitErrorRevertBytes(data, conduitKey, conduit);
                  }
              }
              /**
               * @notice An internal function to check if a recipient address implements
               *         onERC721Received for a given tokenId. Note that this check does
               *         not adhere to the safe transfer specification and is only meant
               *         to provide an additional layer of assurance that the recipient
               *         can receive the tokens — any hooks or post-transfer checks will
               *         fail and the caller will be the transfer helper rather than the
               *         ERC721 contract. Note that the conduit is set as the operator, as
               *         it will be the caller once the transfer is performed.
               *
               * @param conduit   The conduit to provide as the operator when calling
               *                  onERC721Received.
               * @param recipient The ERC721 recipient on which to call onERC721Received.
               * @param tokenId   The ERC721 tokenId of the token being transferred.
               */
              function _checkERC721Receiver(
                  address conduit,
                  address recipient,
                  uint256 tokenId
              ) internal {
                  // Check if recipient can receive ERC721 tokens.
                  try
                      IERC721Receiver(recipient).onERC721Received(
                          conduit,
                          msg.sender,
                          tokenId,
                          ""
                      )
                  returns (bytes4 selector) {
                      // Check if onERC721Received selector is valid.
                      if (selector != IERC721Receiver.onERC721Received.selector) {
                          // Revert if recipient cannot accept
                          // ERC721 tokens.
                          revert InvalidERC721Recipient(recipient);
                      }
                  } catch (bytes memory data) {
                      // "Bubble up" recipient's revert reason.
                      revert ERC721ReceiverErrorRevertBytes(
                          data,
                          recipient,
                          msg.sender,
                          tokenId
                      );
                  } catch Error(string memory reason) {
                      // "Bubble up" recipient's revert reason.
                      revert ERC721ReceiverErrorRevertString(
                          reason,
                          recipient,
                          msg.sender,
                          tokenId
                      );
                  }
              }
              /**
               * @notice An internal function that reverts if the passed-in recipient
               *         is the zero address.
               *
               * @param recipient The recipient on which to perform the check.
               */
              function _checkRecipientIsNotZeroAddress(address recipient) internal pure {
                  // Revert if the recipient is the zero address.
                  if (recipient == address(0x0)) {
                      revert RecipientCannotBeZeroAddress();
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          interface IERC721Receiver {
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import { ConduitItemType } from "../conduit/lib/ConduitEnums.sol";
          /**
           * @dev A TransferHelperItem specifies the itemType (ERC20/ERC721/ERC1155),
           *      token address, token identifier, and amount of the token to be
           *      transferred via the TransferHelper. For ERC20 tokens, identifier
           *      must be 0. For ERC721 tokens, amount must be 1.
           */
          struct TransferHelperItem {
              ConduitItemType itemType;
              address token;
              uint256 identifier;
              uint256 amount;
          }
          /**
           * @dev A TransferHelperItemsWithRecipient specifies the tokens to transfer
           *      via the TransferHelper, their intended recipient, and a boolean flag
           *      indicating whether onERC721Received should be called on a recipient
           *      contract.
           */
          struct TransferHelperItemsWithRecipient {
              TransferHelperItem[] items;
              address recipient;
              bool validateERC721Receiver;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import {
              ConduitTransfer,
              ConduitBatch1155Transfer
          } from "../conduit/lib/ConduitStructs.sol";
          /**
           * @title ConduitInterface
           * @author 0age
           * @notice ConduitInterface contains all external function interfaces, events,
           *         and errors for conduit contracts.
           */
          interface ConduitInterface {
              /**
               * @dev Revert with an error when attempting to execute transfers using a
               *      caller that does not have an open channel.
               */
              error ChannelClosed(address channel);
              /**
               * @dev Revert with an error when attempting to update a channel to the
               *      current status of that channel.
               */
              error ChannelStatusAlreadySet(address channel, bool isOpen);
              /**
               * @dev Revert with an error when attempting to execute a transfer for an
               *      item that does not have an ERC20/721/1155 item type.
               */
              error InvalidItemType();
              /**
               * @dev Revert with an error when attempting to update the status of a
               *      channel from a caller that is not the conduit controller.
               */
              error InvalidController();
              /**
               * @dev Emit an event whenever a channel is opened or closed.
               *
               * @param channel The channel that has been updated.
               * @param open    A boolean indicating whether the conduit is open or not.
               */
              event ChannelUpdated(address indexed channel, bool open);
              /**
               * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller
               *         with an open channel can call this function.
               *
               * @param transfers The ERC20/721/1155 transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function execute(ConduitTransfer[] calldata transfers)
                  external
                  returns (bytes4 magicValue);
              /**
               * @notice Execute a sequence of batch 1155 transfers. Only a caller with an
               *         open channel can call this function.
               *
               * @param batch1155Transfers The 1155 batch transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function executeBatch1155(
                  ConduitBatch1155Transfer[] calldata batch1155Transfers
              ) external returns (bytes4 magicValue);
              /**
               * @notice Execute a sequence of transfers, both single and batch 1155. Only
               *         a caller with an open channel can call this function.
               *
               * @param standardTransfers  The ERC20/721/1155 transfers to perform.
               * @param batch1155Transfers The 1155 batch transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function executeWithBatch1155(
                  ConduitTransfer[] calldata standardTransfers,
                  ConduitBatch1155Transfer[] calldata batch1155Transfers
              ) external returns (bytes4 magicValue);
              /**
               * @notice Open or close a given channel. Only callable by the controller.
               *
               * @param channel The channel to open or close.
               * @param isOpen  The status of the channel (either open or closed).
               */
              function updateChannel(address channel, bool isOpen) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          /**
           * @title ConduitControllerInterface
           * @author 0age
           * @notice ConduitControllerInterface contains all external function interfaces,
           *         structs, events, and errors for the conduit controller.
           */
          interface ConduitControllerInterface {
              /**
               * @dev Track the conduit key, current owner, new potential owner, and open
               *      channels for each deployed conduit.
               */
              struct ConduitProperties {
                  bytes32 key;
                  address owner;
                  address potentialOwner;
                  address[] channels;
                  mapping(address => uint256) channelIndexesPlusOne;
              }
              /**
               * @dev Emit an event whenever a new conduit is created.
               *
               * @param conduit    The newly created conduit.
               * @param conduitKey The conduit key used to create the new conduit.
               */
              event NewConduit(address conduit, bytes32 conduitKey);
              /**
               * @dev Emit an event whenever conduit ownership is transferred.
               *
               * @param conduit       The conduit for which ownership has been
               *                      transferred.
               * @param previousOwner The previous owner of the conduit.
               * @param newOwner      The new owner of the conduit.
               */
              event OwnershipTransferred(
                  address indexed conduit,
                  address indexed previousOwner,
                  address indexed newOwner
              );
              /**
               * @dev Emit an event whenever a conduit owner registers a new potential
               *      owner for that conduit.
               *
               * @param newPotentialOwner The new potential owner of the conduit.
               */
              event PotentialOwnerUpdated(address indexed newPotentialOwner);
              /**
               * @dev Revert with an error when attempting to create a new conduit using a
               *      conduit key where the first twenty bytes of the key do not match the
               *      address of the caller.
               */
              error InvalidCreator();
              /**
               * @dev Revert with an error when attempting to create a new conduit when no
               *      initial owner address is supplied.
               */
              error InvalidInitialOwner();
              /**
               * @dev Revert with an error when attempting to set a new potential owner
               *      that is already set.
               */
              error NewPotentialOwnerAlreadySet(
                  address conduit,
                  address newPotentialOwner
              );
              /**
               * @dev Revert with an error when attempting to cancel ownership transfer
               *      when no new potential owner is currently set.
               */
              error NoPotentialOwnerCurrentlySet(address conduit);
              /**
               * @dev Revert with an error when attempting to interact with a conduit that
               *      does not yet exist.
               */
              error NoConduit();
              /**
               * @dev Revert with an error when attempting to create a conduit that
               *      already exists.
               */
              error ConduitAlreadyExists(address conduit);
              /**
               * @dev Revert with an error when attempting to update channels or transfer
               *      ownership of a conduit when the caller is not the owner of the
               *      conduit in question.
               */
              error CallerIsNotOwner(address conduit);
              /**
               * @dev Revert with an error when attempting to register a new potential
               *      owner and supplying the null address.
               */
              error NewPotentialOwnerIsZeroAddress(address conduit);
              /**
               * @dev Revert with an error when attempting to claim ownership of a conduit
               *      with a caller that is not the current potential owner for the
               *      conduit in question.
               */
              error CallerIsNotNewPotentialOwner(address conduit);
              /**
               * @dev Revert with an error when attempting to retrieve a channel using an
               *      index that is out of range.
               */
              error ChannelOutOfRange(address conduit);
              /**
               * @notice Deploy a new conduit using a supplied conduit key and assigning
               *         an initial owner for the deployed conduit. Note that the first
               *         twenty bytes of the supplied conduit key must match the caller
               *         and that a new conduit cannot be created if one has already been
               *         deployed using the same conduit key.
               *
               * @param conduitKey   The conduit key used to deploy the conduit. Note that
               *                     the first twenty bytes of the conduit key must match
               *                     the caller of this contract.
               * @param initialOwner The initial owner to set for the new conduit.
               *
               * @return conduit The address of the newly deployed conduit.
               */
              function createConduit(bytes32 conduitKey, address initialOwner)
                  external
                  returns (address conduit);
              /**
               * @notice Open or close a channel on a given conduit, thereby allowing the
               *         specified account to execute transfers against that conduit.
               *         Extreme care must be taken when updating channels, as malicious
               *         or vulnerable channels can transfer any ERC20, ERC721 and ERC1155
               *         tokens where the token holder has granted the conduit approval.
               *         Only the owner of the conduit in question may call this function.
               *
               * @param conduit The conduit for which to open or close the channel.
               * @param channel The channel to open or close on the conduit.
               * @param isOpen  A boolean indicating whether to open or close the channel.
               */
              function updateChannel(
                  address conduit,
                  address channel,
                  bool isOpen
              ) external;
              /**
               * @notice Initiate conduit ownership transfer by assigning a new potential
               *         owner for the given conduit. Once set, the new potential owner
               *         may call `acceptOwnership` to claim ownership of the conduit.
               *         Only the owner of the conduit in question may call this function.
               *
               * @param conduit The conduit for which to initiate ownership transfer.
               * @param newPotentialOwner The new potential owner of the conduit.
               */
              function transferOwnership(address conduit, address newPotentialOwner)
                  external;
              /**
               * @notice Clear the currently set potential owner, if any, from a conduit.
               *         Only the owner of the conduit in question may call this function.
               *
               * @param conduit The conduit for which to cancel ownership transfer.
               */
              function cancelOwnershipTransfer(address conduit) external;
              /**
               * @notice Accept ownership of a supplied conduit. Only accounts that the
               *         current owner has set as the new potential owner may call this
               *         function.
               *
               * @param conduit The conduit for which to accept ownership.
               */
              function acceptOwnership(address conduit) external;
              /**
               * @notice Retrieve the current owner of a deployed conduit.
               *
               * @param conduit The conduit for which to retrieve the associated owner.
               *
               * @return owner The owner of the supplied conduit.
               */
              function ownerOf(address conduit) external view returns (address owner);
              /**
               * @notice Retrieve the conduit key for a deployed conduit via reverse
               *         lookup.
               *
               * @param conduit The conduit for which to retrieve the associated conduit
               *                key.
               *
               * @return conduitKey The conduit key used to deploy the supplied conduit.
               */
              function getKey(address conduit) external view returns (bytes32 conduitKey);
              /**
               * @notice Derive the conduit associated with a given conduit key and
               *         determine whether that conduit exists (i.e. whether it has been
               *         deployed).
               *
               * @param conduitKey The conduit key used to derive the conduit.
               *
               * @return conduit The derived address of the conduit.
               * @return exists  A boolean indicating whether the derived conduit has been
               *                 deployed or not.
               */
              function getConduit(bytes32 conduitKey)
                  external
                  view
                  returns (address conduit, bool exists);
              /**
               * @notice Retrieve the potential owner, if any, for a given conduit. The
               *         current owner may set a new potential owner via
               *         `transferOwnership` and that owner may then accept ownership of
               *         the conduit in question via `acceptOwnership`.
               *
               * @param conduit The conduit for which to retrieve the potential owner.
               *
               * @return potentialOwner The potential owner, if any, for the conduit.
               */
              function getPotentialOwner(address conduit)
                  external
                  view
                  returns (address potentialOwner);
              /**
               * @notice Retrieve the status (either open or closed) of a given channel on
               *         a conduit.
               *
               * @param conduit The conduit for which to retrieve the channel status.
               * @param channel The channel for which to retrieve the status.
               *
               * @return isOpen The status of the channel on the given conduit.
               */
              function getChannelStatus(address conduit, address channel)
                  external
                  view
                  returns (bool isOpen);
              /**
               * @notice Retrieve the total number of open channels for a given conduit.
               *
               * @param conduit The conduit for which to retrieve the total channel count.
               *
               * @return totalChannels The total number of open channels for the conduit.
               */
              function getTotalChannels(address conduit)
                  external
                  view
                  returns (uint256 totalChannels);
              /**
               * @notice Retrieve an open channel at a specific index for a given conduit.
               *         Note that the index of a channel can change as a result of other
               *         channels being closed on the conduit.
               *
               * @param conduit      The conduit for which to retrieve the open channel.
               * @param channelIndex The index of the channel in question.
               *
               * @return channel The open channel, if any, at the specified channel index.
               */
              function getChannel(address conduit, uint256 channelIndex)
                  external
                  view
                  returns (address channel);
              /**
               * @notice Retrieve all open channels for a given conduit. Note that calling
               *         this function for a conduit with many channels will revert with
               *         an out-of-gas error.
               *
               * @param conduit The conduit for which to retrieve open channels.
               *
               * @return channels An array of open channels on the given conduit.
               */
              function getChannels(address conduit)
                  external
                  view
                  returns (address[] memory channels);
              /**
               * @dev Retrieve the conduit creation code and runtime code hashes.
               */
              function getConduitCodeHashes()
                  external
                  view
                  returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
          import { ConduitItemType } from "./lib/ConduitEnums.sol";
          import { TokenTransferrer } from "../lib/TokenTransferrer.sol";
          import {
              ConduitTransfer,
              ConduitBatch1155Transfer
          } from "./lib/ConduitStructs.sol";
          import "./lib/ConduitConstants.sol";
          /**
           * @title Conduit
           * @author 0age
           * @notice This contract serves as an originator for "proxied" transfers. Each
           *         conduit is deployed and controlled by a "conduit controller" that can
           *         add and remove "channels" or contracts that can instruct the conduit
           *         to transfer approved ERC20/721/1155 tokens. *IMPORTANT NOTE: each
           *         conduit has an owner that can arbitrarily add or remove channels, and
           *         a malicious or negligent owner can add a channel that allows for any
           *         approved ERC20/721/1155 tokens to be taken immediately — be extremely
           *         cautious with what conduits you give token approvals to!*
           */
          contract Conduit is ConduitInterface, TokenTransferrer {
              // Set deployer as an immutable controller that can update channel statuses.
              address private immutable _controller;
              // Track the status of each channel.
              mapping(address => bool) private _channels;
              /**
               * @notice Ensure that the caller is currently registered as an open channel
               *         on the conduit.
               */
              modifier onlyOpenChannel() {
                  // Utilize assembly to access channel storage mapping directly.
                  assembly {
                      // Write the caller to scratch space.
                      mstore(ChannelKey_channel_ptr, caller())
                      // Write the storage slot for _channels to scratch space.
                      mstore(ChannelKey_slot_ptr, _channels.slot)
                      // Derive the position in storage of _channels[msg.sender]
                      // and check if the stored value is zero.
                      if iszero(
                          sload(keccak256(ChannelKey_channel_ptr, ChannelKey_length))
                      ) {
                          // The caller is not an open channel; revert with
                          // ChannelClosed(caller). First, set error signature in memory.
                          mstore(ChannelClosed_error_ptr, ChannelClosed_error_signature)
                          // Next, set the caller as the argument.
                          mstore(ChannelClosed_channel_ptr, caller())
                          // Finally, revert, returning full custom error with argument.
                          revert(ChannelClosed_error_ptr, ChannelClosed_error_length)
                      }
                  }
                  // Continue with function execution.
                  _;
              }
              /**
               * @notice In the constructor, set the deployer as the controller.
               */
              constructor() {
                  // Set the deployer as the controller.
                  _controller = msg.sender;
              }
              /**
               * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller
               *         with an open channel can call this function. Note that channels
               *         are expected to implement reentrancy protection if desired, and
               *         that cross-channel reentrancy may be possible if the conduit has
               *         multiple open channels at once. Also note that channels are
               *         expected to implement checks against transferring any zero-amount
               *         items if that constraint is desired.
               *
               * @param transfers The ERC20/721/1155 transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function execute(ConduitTransfer[] calldata transfers)
                  external
                  override
                  onlyOpenChannel
                  returns (bytes4 magicValue)
              {
                  // Retrieve the total number of transfers and place on the stack.
                  uint256 totalStandardTransfers = transfers.length;
                  // Iterate over each transfer.
                  for (uint256 i = 0; i < totalStandardTransfers; ) {
                      // Retrieve the transfer in question and perform the transfer.
                      _transfer(transfers[i]);
                      // Skip overflow check as for loop is indexed starting at zero.
                      unchecked {
                          ++i;
                      }
                  }
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.execute.selector;
              }
              /**
               * @notice Execute a sequence of batch 1155 item transfers. Only a caller
               *         with an open channel can call this function. Note that channels
               *         are expected to implement reentrancy protection if desired, and
               *         that cross-channel reentrancy may be possible if the conduit has
               *         multiple open channels at once. Also note that channels are
               *         expected to implement checks against transferring any zero-amount
               *         items if that constraint is desired.
               *
               * @param batchTransfers The 1155 batch item transfers to perform.
               *
               * @return magicValue A magic value indicating that the item transfers were
               *                    performed successfully.
               */
              function executeBatch1155(
                  ConduitBatch1155Transfer[] calldata batchTransfers
              ) external override onlyOpenChannel returns (bytes4 magicValue) {
                  // Perform 1155 batch transfers. Note that memory should be considered
                  // entirely corrupted from this point forward.
                  _performERC1155BatchTransfers(batchTransfers);
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.executeBatch1155.selector;
              }
              /**
               * @notice Execute a sequence of transfers, both single ERC20/721/1155 item
               *         transfers as well as batch 1155 item transfers. Only a caller
               *         with an open channel can call this function. Note that channels
               *         are expected to implement reentrancy protection if desired, and
               *         that cross-channel reentrancy may be possible if the conduit has
               *         multiple open channels at once. Also note that channels are
               *         expected to implement checks against transferring any zero-amount
               *         items if that constraint is desired.
               *
               * @param standardTransfers The ERC20/721/1155 item transfers to perform.
               * @param batchTransfers    The 1155 batch item transfers to perform.
               *
               * @return magicValue A magic value indicating that the item transfers were
               *                    performed successfully.
               */
              function executeWithBatch1155(
                  ConduitTransfer[] calldata standardTransfers,
                  ConduitBatch1155Transfer[] calldata batchTransfers
              ) external override onlyOpenChannel returns (bytes4 magicValue) {
                  // Retrieve the total number of transfers and place on the stack.
                  uint256 totalStandardTransfers = standardTransfers.length;
                  // Iterate over each standard transfer.
                  for (uint256 i = 0; i < totalStandardTransfers; ) {
                      // Retrieve the transfer in question and perform the transfer.
                      _transfer(standardTransfers[i]);
                      // Skip overflow check as for loop is indexed starting at zero.
                      unchecked {
                          ++i;
                      }
                  }
                  // Perform 1155 batch transfers. Note that memory should be considered
                  // entirely corrupted from this point forward aside from the free memory
                  // pointer having the default value.
                  _performERC1155BatchTransfers(batchTransfers);
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.executeWithBatch1155.selector;
              }
              /**
               * @notice Open or close a given channel. Only callable by the controller.
               *
               * @param channel The channel to open or close.
               * @param isOpen  The status of the channel (either open or closed).
               */
              function updateChannel(address channel, bool isOpen) external override {
                  // Ensure that the caller is the controller of this contract.
                  if (msg.sender != _controller) {
                      revert InvalidController();
                  }
                  // Ensure that the channel does not already have the indicated status.
                  if (_channels[channel] == isOpen) {
                      revert ChannelStatusAlreadySet(channel, isOpen);
                  }
                  // Update the status of the channel.
                  _channels[channel] = isOpen;
                  // Emit a corresponding event.
                  emit ChannelUpdated(channel, isOpen);
              }
              /**
               * @dev Internal function to transfer a given ERC20/721/1155 item. Note that
               *      channels are expected to implement checks against transferring any
               *      zero-amount items if that constraint is desired.
               *
               * @param item The ERC20/721/1155 item to transfer.
               */
              function _transfer(ConduitTransfer calldata item) internal {
                  // Determine the transfer method based on the respective item type.
                  if (item.itemType == ConduitItemType.ERC20) {
                      // Transfer ERC20 token. Note that item.identifier is ignored and
                      // therefore ERC20 transfer items are potentially malleable — this
                      // check should be performed by the calling channel if a constraint
                      // on item malleability is desired.
                      _performERC20Transfer(item.token, item.from, item.to, item.amount);
                  } else if (item.itemType == ConduitItemType.ERC721) {
                      // Ensure that exactly one 721 item is being transferred.
                      if (item.amount != 1) {
                          revert InvalidERC721TransferAmount();
                      }
                      // Transfer ERC721 token.
                      _performERC721Transfer(
                          item.token,
                          item.from,
                          item.to,
                          item.identifier
                      );
                  } else if (item.itemType == ConduitItemType.ERC1155) {
                      // Transfer ERC1155 token.
                      _performERC1155Transfer(
                          item.token,
                          item.from,
                          item.to,
                          item.identifier,
                          item.amount
                      );
                  } else {
                      // Throw with an error.
                      revert InvalidItemType();
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import { ConduitItemType } from "./ConduitEnums.sol";
          struct ConduitTransfer {
              ConduitItemType itemType;
              address token;
              address from;
              address to;
              uint256 identifier;
              uint256 amount;
          }
          struct ConduitBatch1155Transfer {
              address token;
              address from;
              address to;
              uint256[] ids;
              uint256[] amounts;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import {
              TransferHelperItem,
              TransferHelperItemsWithRecipient
          } from "../helpers/TransferHelperStructs.sol";
          interface TransferHelperInterface {
              /**
               * @notice Transfer multiple items to a single recipient.
               *
               * @param items The items to transfer.
               * @param conduitKey  The key of the conduit performing the bulk transfer.
               */
              function bulkTransfer(
                  TransferHelperItemsWithRecipient[] calldata items,
                  bytes32 conduitKey
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          /**
           * @title TransferHelperErrors
           */
          interface TransferHelperErrors {
              /**
               * @dev Revert with an error when attempting to execute transfers with a
               *      NATIVE itemType.
               */
              error InvalidItemType();
              /**
               * @dev Revert with an error when an ERC721 transfer with amount other than
               *      one is attempted.
               */
              error InvalidERC721TransferAmount();
              /**
               * @dev Revert with an error when attempting to execute an ERC721 transfer
               *      to an invalid recipient.
               */
              error InvalidERC721Recipient(address recipient);
              /**
               * @dev Revert with an error when a call to a ERC721 receiver reverts with
               *      bytes data.
               */
              error ERC721ReceiverErrorRevertBytes(
                  bytes reason,
                  address receiver,
                  address sender,
                  uint256 identifier
              );
              /**
               * @dev Revert with an error when a call to a ERC721 receiver reverts with
               *      string reason.
               */
              error ERC721ReceiverErrorRevertString(
                  string reason,
                  address receiver,
                  address sender,
                  uint256 identifier
              );
              /**
               * @dev Revert with an error when an ERC20 token has an invalid identifier.
               */
              error InvalidERC20Identifier();
              /**
               * @dev Revert with an error if the recipient is the zero address.
               */
              error RecipientCannotBeZeroAddress();
              /**
               * @dev Revert with an error when attempting to fill an order referencing an
               *      invalid conduit (i.e. one that has not been deployed).
               */
              error InvalidConduit(bytes32 conduitKey, address conduit);
              /**
               * @dev Revert with an error when a call to a conduit reverts with a
               *      reason string.
               */
              error ConduitErrorRevertString(
                  string reason,
                  bytes32 conduitKey,
                  address conduit
              );
              /**
               * @dev Revert with an error when a call to a conduit reverts with bytes
               *      data.
               */
              error ConduitErrorRevertBytes(
                  bytes reason,
                  bytes32 conduitKey,
                  address conduit
              );
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          enum ConduitItemType {
              NATIVE, // unused
              ERC20,
              ERC721,
              ERC1155
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          import "./TokenTransferrerConstants.sol";
          import {
              TokenTransferrerErrors
          } from "../interfaces/TokenTransferrerErrors.sol";
          import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol";
          /**
           * @title TokenTransferrer
           * @author 0age
           * @custom:coauthor d1ll0n
           * @custom:coauthor transmissions11
           * @notice TokenTransferrer is a library for performing optimized ERC20, ERC721,
           *         ERC1155, and batch ERC1155 transfers, used by both Seaport as well as
           *         by conduits deployed by the ConduitController. Use great caution when
           *         considering these functions for use in other codebases, as there are
           *         significant side effects and edge cases that need to be thoroughly
           *         understood and carefully addressed.
           */
          contract TokenTransferrer is TokenTransferrerErrors {
              /**
               * @dev Internal function to transfer ERC20 tokens from a given originator
               *      to a given recipient. Sufficient approvals must be set on the
               *      contract performing the transfer.
               *
               * @param token      The ERC20 token to transfer.
               * @param from       The originator of the transfer.
               * @param to         The recipient of the transfer.
               * @param amount     The amount to transfer.
               */
              function _performERC20Transfer(
                  address token,
                  address from,
                  address to,
                  uint256 amount
              ) internal {
                  // Utilize assembly to perform an optimized ERC20 token transfer.
                  assembly {
                      // The free memory pointer memory slot will be used when populating
                      // call data for the transfer; read the value and restore it later.
                      let memPointer := mload(FreeMemoryPointerSlot)
                      // Write call data into memory, starting with function selector.
                      mstore(ERC20_transferFrom_sig_ptr, ERC20_transferFrom_signature)
                      mstore(ERC20_transferFrom_from_ptr, from)
                      mstore(ERC20_transferFrom_to_ptr, to)
                      mstore(ERC20_transferFrom_amount_ptr, amount)
                      // Make call & copy up to 32 bytes of return data to scratch space.
                      // Scratch space does not need to be cleared ahead of time, as the
                      // subsequent check will ensure that either at least a full word of
                      // return data is received (in which case it will be overwritten) or
                      // that no data is received (in which case scratch space will be
                      // ignored) on a successful call to the given token.
                      let callStatus := call(
                          gas(),
                          token,
                          0,
                          ERC20_transferFrom_sig_ptr,
                          ERC20_transferFrom_length,
                          0,
                          OneWord
                      )
                      // Determine whether transfer was successful using status & result.
                      let success := and(
                          // Set success to whether the call reverted, if not check it
                          // either returned exactly 1 (can't just be non-zero data), or
                          // had no return data.
                          or(
                              and(eq(mload(0), 1), gt(returndatasize(), 31)),
                              iszero(returndatasize())
                          ),
                          callStatus
                      )
                      // Handle cases where either the transfer failed or no data was
                      // returned. Group these, as most transfers will succeed with data.
                      // Equivalent to `or(iszero(success), iszero(returndatasize()))`
                      // but after it's inverted for JUMPI this expression is cheaper.
                      if iszero(and(success, iszero(iszero(returndatasize())))) {
                          // If the token has no code or the transfer failed: Equivalent
                          // to `or(iszero(success), iszero(extcodesize(token)))` but
                          // after it's inverted for JUMPI this expression is cheaper.
                          if iszero(and(iszero(iszero(extcodesize(token))), success)) {
                              // If the transfer failed:
                              if iszero(success) {
                                  // If it was due to a revert:
                                  if iszero(callStatus) {
                                      // If it returned a message, bubble it up as long as
                                      // sufficient gas remains to do so:
                                      if returndatasize() {
                                          // Ensure that sufficient gas is available to
                                          // copy returndata while expanding memory where
                                          // necessary. Start by computing the word size
                                          // of returndata and allocated memory. Round up
                                          // to the nearest full word.
                                          let returnDataWords := div(
                                              add(returndatasize(), AlmostOneWord),
                                              OneWord
                                          )
                                          // Note: use the free memory pointer in place of
                                          // msize() to work around a Yul warning that
                                          // prevents accessing msize directly when the IR
                                          // pipeline is activated.
                                          let msizeWords := div(memPointer, OneWord)
                                          // Next, compute the cost of the returndatacopy.
                                          let cost := mul(CostPerWord, returnDataWords)
                                          // Then, compute cost of new memory allocation.
                                          if gt(returnDataWords, msizeWords) {
                                              cost := add(
                                                  cost,
                                                  add(
                                                      mul(
                                                          sub(
                                                              returnDataWords,
                                                              msizeWords
                                                          ),
                                                          CostPerWord
                                                      ),
                                                      div(
                                                          sub(
                                                              mul(
                                                                  returnDataWords,
                                                                  returnDataWords
                                                              ),
                                                              mul(msizeWords, msizeWords)
                                                          ),
                                                          MemoryExpansionCoefficient
                                                      )
                                                  )
                                              )
                                          }
                                          // Finally, add a small constant and compare to
                                          // gas remaining; bubble up the revert data if
                                          // enough gas is still available.
                                          if lt(add(cost, ExtraGasBuffer), gas()) {
                                              // Copy returndata to memory; overwrite
                                              // existing memory.
                                              returndatacopy(0, 0, returndatasize())
                                              // Revert, specifying memory region with
                                              // copied returndata.
                                              revert(0, returndatasize())
                                          }
                                      }
                                      // Otherwise revert with a generic error message.
                                      mstore(
                                          TokenTransferGenericFailure_error_sig_ptr,
                                          TokenTransferGenericFailure_error_signature
                                      )
                                      mstore(
                                          TokenTransferGenericFailure_error_token_ptr,
                                          token
                                      )
                                      mstore(
                                          TokenTransferGenericFailure_error_from_ptr,
                                          from
                                      )
                                      mstore(TokenTransferGenericFailure_error_to_ptr, to)
                                      mstore(TokenTransferGenericFailure_error_id_ptr, 0)
                                      mstore(
                                          TokenTransferGenericFailure_error_amount_ptr,
                                          amount
                                      )
                                      revert(
                                          TokenTransferGenericFailure_error_sig_ptr,
                                          TokenTransferGenericFailure_error_length
                                      )
                                  }
                                  // Otherwise revert with a message about the token
                                  // returning false or non-compliant return values.
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_sig_ptr,
                                      BadReturnValueFromERC20OnTransfer_error_signature
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_token_ptr,
                                      token
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_from_ptr,
                                      from
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_to_ptr,
                                      to
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_amount_ptr,
                                      amount
                                  )
                                  revert(
                                      BadReturnValueFromERC20OnTransfer_error_sig_ptr,
                                      BadReturnValueFromERC20OnTransfer_error_length
                                  )
                              }
                              // Otherwise, revert with error about token not having code:
                              mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                              mstore(NoContract_error_token_ptr, token)
                              revert(NoContract_error_sig_ptr, NoContract_error_length)
                          }
                          // Otherwise, the token just returned no data despite the call
                          // having succeeded; no need to optimize for this as it's not
                          // technically ERC20 compliant.
                      }
                      // Restore the original free memory pointer.
                      mstore(FreeMemoryPointerSlot, memPointer)
                      // Restore the zero slot to zero.
                      mstore(ZeroSlot, 0)
                  }
              }
              /**
               * @dev Internal function to transfer an ERC721 token from a given
               *      originator to a given recipient. Sufficient approvals must be set on
               *      the contract performing the transfer. Note that this function does
               *      not check whether the receiver can accept the ERC721 token (i.e. it
               *      does not use `safeTransferFrom`).
               *
               * @param token      The ERC721 token to transfer.
               * @param from       The originator of the transfer.
               * @param to         The recipient of the transfer.
               * @param identifier The tokenId to transfer.
               */
              function _performERC721Transfer(
                  address token,
                  address from,
                  address to,
                  uint256 identifier
              ) internal {
                  // Utilize assembly to perform an optimized ERC721 token transfer.
                  assembly {
                      // If the token has no code, revert.
                      if iszero(extcodesize(token)) {
                          mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                          mstore(NoContract_error_token_ptr, token)
                          revert(NoContract_error_sig_ptr, NoContract_error_length)
                      }
                      // The free memory pointer memory slot will be used when populating
                      // call data for the transfer; read the value and restore it later.
                      let memPointer := mload(FreeMemoryPointerSlot)
                      // Write call data to memory starting with function selector.
                      mstore(ERC721_transferFrom_sig_ptr, ERC721_transferFrom_signature)
                      mstore(ERC721_transferFrom_from_ptr, from)
                      mstore(ERC721_transferFrom_to_ptr, to)
                      mstore(ERC721_transferFrom_id_ptr, identifier)
                      // Perform the call, ignoring return data.
                      let success := call(
                          gas(),
                          token,
                          0,
                          ERC721_transferFrom_sig_ptr,
                          ERC721_transferFrom_length,
                          0,
                          0
                      )
                      // If the transfer reverted:
                      if iszero(success) {
                          // If it returned a message, bubble it up as long as sufficient
                          // gas remains to do so:
                          if returndatasize() {
                              // Ensure that sufficient gas is available to copy
                              // returndata while expanding memory where necessary. Start
                              // by computing word size of returndata & allocated memory.
                              // Round up to the nearest full word.
                              let returnDataWords := div(
                                  add(returndatasize(), AlmostOneWord),
                                  OneWord
                              )
                              // Note: use the free memory pointer in place of msize() to
                              // work around a Yul warning that prevents accessing msize
                              // directly when the IR pipeline is activated.
                              let msizeWords := div(memPointer, OneWord)
                              // Next, compute the cost of the returndatacopy.
                              let cost := mul(CostPerWord, returnDataWords)
                              // Then, compute cost of new memory allocation.
                              if gt(returnDataWords, msizeWords) {
                                  cost := add(
                                      cost,
                                      add(
                                          mul(
                                              sub(returnDataWords, msizeWords),
                                              CostPerWord
                                          ),
                                          div(
                                              sub(
                                                  mul(returnDataWords, returnDataWords),
                                                  mul(msizeWords, msizeWords)
                                              ),
                                              MemoryExpansionCoefficient
                                          )
                                      )
                                  )
                              }
                              // Finally, add a small constant and compare to gas
                              // remaining; bubble up the revert data if enough gas is
                              // still available.
                              if lt(add(cost, ExtraGasBuffer), gas()) {
                                  // Copy returndata to memory; overwrite existing memory.
                                  returndatacopy(0, 0, returndatasize())
                                  // Revert, giving memory region with copied returndata.
                                  revert(0, returndatasize())
                              }
                          }
                          // Otherwise revert with a generic error message.
                          mstore(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_signature
                          )
                          mstore(TokenTransferGenericFailure_error_token_ptr, token)
                          mstore(TokenTransferGenericFailure_error_from_ptr, from)
                          mstore(TokenTransferGenericFailure_error_to_ptr, to)
                          mstore(TokenTransferGenericFailure_error_id_ptr, identifier)
                          mstore(TokenTransferGenericFailure_error_amount_ptr, 1)
                          revert(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_length
                          )
                      }
                      // Restore the original free memory pointer.
                      mstore(FreeMemoryPointerSlot, memPointer)
                      // Restore the zero slot to zero.
                      mstore(ZeroSlot, 0)
                  }
              }
              /**
               * @dev Internal function to transfer ERC1155 tokens from a given
               *      originator to a given recipient. Sufficient approvals must be set on
               *      the contract performing the transfer and contract recipients must
               *      implement the ERC1155TokenReceiver interface to indicate that they
               *      are willing to accept the transfer.
               *
               * @param token      The ERC1155 token to transfer.
               * @param from       The originator of the transfer.
               * @param to         The recipient of the transfer.
               * @param identifier The id to transfer.
               * @param amount     The amount to transfer.
               */
              function _performERC1155Transfer(
                  address token,
                  address from,
                  address to,
                  uint256 identifier,
                  uint256 amount
              ) internal {
                  // Utilize assembly to perform an optimized ERC1155 token transfer.
                  assembly {
                      // If the token has no code, revert.
                      if iszero(extcodesize(token)) {
                          mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                          mstore(NoContract_error_token_ptr, token)
                          revert(NoContract_error_sig_ptr, NoContract_error_length)
                      }
                      // The following memory slots will be used when populating call data
                      // for the transfer; read the values and restore them later.
                      let memPointer := mload(FreeMemoryPointerSlot)
                      let slot0x80 := mload(Slot0x80)
                      let slot0xA0 := mload(Slot0xA0)
                      let slot0xC0 := mload(Slot0xC0)
                      // Write call data into memory, beginning with function selector.
                      mstore(
                          ERC1155_safeTransferFrom_sig_ptr,
                          ERC1155_safeTransferFrom_signature
                      )
                      mstore(ERC1155_safeTransferFrom_from_ptr, from)
                      mstore(ERC1155_safeTransferFrom_to_ptr, to)
                      mstore(ERC1155_safeTransferFrom_id_ptr, identifier)
                      mstore(ERC1155_safeTransferFrom_amount_ptr, amount)
                      mstore(
                          ERC1155_safeTransferFrom_data_offset_ptr,
                          ERC1155_safeTransferFrom_data_length_offset
                      )
                      mstore(ERC1155_safeTransferFrom_data_length_ptr, 0)
                      // Perform the call, ignoring return data.
                      let success := call(
                          gas(),
                          token,
                          0,
                          ERC1155_safeTransferFrom_sig_ptr,
                          ERC1155_safeTransferFrom_length,
                          0,
                          0
                      )
                      // If the transfer reverted:
                      if iszero(success) {
                          // If it returned a message, bubble it up as long as sufficient
                          // gas remains to do so:
                          if returndatasize() {
                              // Ensure that sufficient gas is available to copy
                              // returndata while expanding memory where necessary. Start
                              // by computing word size of returndata & allocated memory.
                              // Round up to the nearest full word.
                              let returnDataWords := div(
                                  add(returndatasize(), AlmostOneWord),
                                  OneWord
                              )
                              // Note: use the free memory pointer in place of msize() to
                              // work around a Yul warning that prevents accessing msize
                              // directly when the IR pipeline is activated.
                              let msizeWords := div(memPointer, OneWord)
                              // Next, compute the cost of the returndatacopy.
                              let cost := mul(CostPerWord, returnDataWords)
                              // Then, compute cost of new memory allocation.
                              if gt(returnDataWords, msizeWords) {
                                  cost := add(
                                      cost,
                                      add(
                                          mul(
                                              sub(returnDataWords, msizeWords),
                                              CostPerWord
                                          ),
                                          div(
                                              sub(
                                                  mul(returnDataWords, returnDataWords),
                                                  mul(msizeWords, msizeWords)
                                              ),
                                              MemoryExpansionCoefficient
                                          )
                                      )
                                  )
                              }
                              // Finally, add a small constant and compare to gas
                              // remaining; bubble up the revert data if enough gas is
                              // still available.
                              if lt(add(cost, ExtraGasBuffer), gas()) {
                                  // Copy returndata to memory; overwrite existing memory.
                                  returndatacopy(0, 0, returndatasize())
                                  // Revert, giving memory region with copied returndata.
                                  revert(0, returndatasize())
                              }
                          }
                          // Otherwise revert with a generic error message.
                          mstore(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_signature
                          )
                          mstore(TokenTransferGenericFailure_error_token_ptr, token)
                          mstore(TokenTransferGenericFailure_error_from_ptr, from)
                          mstore(TokenTransferGenericFailure_error_to_ptr, to)
                          mstore(TokenTransferGenericFailure_error_id_ptr, identifier)
                          mstore(TokenTransferGenericFailure_error_amount_ptr, amount)
                          revert(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_length
                          )
                      }
                      mstore(Slot0x80, slot0x80) // Restore slot 0x80.
                      mstore(Slot0xA0, slot0xA0) // Restore slot 0xA0.
                      mstore(Slot0xC0, slot0xC0) // Restore slot 0xC0.
                      // Restore the original free memory pointer.
                      mstore(FreeMemoryPointerSlot, memPointer)
                      // Restore the zero slot to zero.
                      mstore(ZeroSlot, 0)
                  }
              }
              /**
               * @dev Internal function to transfer ERC1155 tokens from a given
               *      originator to a given recipient. Sufficient approvals must be set on
               *      the contract performing the transfer and contract recipients must
               *      implement the ERC1155TokenReceiver interface to indicate that they
               *      are willing to accept the transfer. NOTE: this function is not
               *      memory-safe; it will overwrite existing memory, restore the free
               *      memory pointer to the default value, and overwrite the zero slot.
               *      This function should only be called once memory is no longer
               *      required and when uninitialized arrays are not utilized, and memory
               *      should be considered fully corrupted (aside from the existence of a
               *      default-value free memory pointer) after calling this function.
               *
               * @param batchTransfers The group of 1155 batch transfers to perform.
               */
              function _performERC1155BatchTransfers(
                  ConduitBatch1155Transfer[] calldata batchTransfers
              ) internal {
                  // Utilize assembly to perform optimized batch 1155 transfers.
                  assembly {
                      let len := batchTransfers.length
                      // Pointer to first head in the array, which is offset to the struct
                      // at each index. This gets incremented after each loop to avoid
                      // multiplying by 32 to get the offset for each element.
                      let nextElementHeadPtr := batchTransfers.offset
                      // Pointer to beginning of the head of the array. This is the
                      // reference position each offset references. It's held static to
                      // let each loop calculate the data position for an element.
                      let arrayHeadPtr := nextElementHeadPtr
                      // Write the function selector, which will be reused for each call:
                      // safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
                      mstore(
                          ConduitBatch1155Transfer_from_offset,
                          ERC1155_safeBatchTransferFrom_signature
                      )
                      // Iterate over each batch transfer.
                      for {
                          let i := 0
                      } lt(i, len) {
                          i := add(i, 1)
                      } {
                          // Read the offset to the beginning of the element and add
                          // it to pointer to the beginning of the array head to get
                          // the absolute position of the element in calldata.
                          let elementPtr := add(
                              arrayHeadPtr,
                              calldataload(nextElementHeadPtr)
                          )
                          // Retrieve the token from calldata.
                          let token := calldataload(elementPtr)
                          // If the token has no code, revert.
                          if iszero(extcodesize(token)) {
                              mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                              mstore(NoContract_error_token_ptr, token)
                              revert(NoContract_error_sig_ptr, NoContract_error_length)
                          }
                          // Get the total number of supplied ids.
                          let idsLength := calldataload(
                              add(elementPtr, ConduitBatch1155Transfer_ids_length_offset)
                          )
                          // Determine the expected offset for the amounts array.
                          let expectedAmountsOffset := add(
                              ConduitBatch1155Transfer_amounts_length_baseOffset,
                              mul(idsLength, OneWord)
                          )
                          // Validate struct encoding.
                          let invalidEncoding := iszero(
                              and(
                                  // ids.length == amounts.length
                                  eq(
                                      idsLength,
                                      calldataload(add(elementPtr, expectedAmountsOffset))
                                  ),
                                  and(
                                      // ids_offset == 0xa0
                                      eq(
                                          calldataload(
                                              add(
                                                  elementPtr,
                                                  ConduitBatch1155Transfer_ids_head_offset
                                              )
                                          ),
                                          ConduitBatch1155Transfer_ids_length_offset
                                      ),
                                      // amounts_offset == 0xc0 + ids.length*32
                                      eq(
                                          calldataload(
                                              add(
                                                  elementPtr,
                                                  ConduitBatchTransfer_amounts_head_offset
                                              )
                                          ),
                                          expectedAmountsOffset
                                      )
                                  )
                              )
                          )
                          // Revert with an error if the encoding is not valid.
                          if invalidEncoding {
                              mstore(
                                  Invalid1155BatchTransferEncoding_ptr,
                                  Invalid1155BatchTransferEncoding_selector
                              )
                              revert(
                                  Invalid1155BatchTransferEncoding_ptr,
                                  Invalid1155BatchTransferEncoding_length
                              )
                          }
                          // Update the offset position for the next loop
                          nextElementHeadPtr := add(nextElementHeadPtr, OneWord)
                          // Copy the first section of calldata (before dynamic values).
                          calldatacopy(
                              BatchTransfer1155Params_ptr,
                              add(elementPtr, ConduitBatch1155Transfer_from_offset),
                              ConduitBatch1155Transfer_usable_head_size
                          )
                          // Determine size of calldata required for ids and amounts. Note
                          // that the size includes both lengths as well as the data.
                          let idsAndAmountsSize := add(TwoWords, mul(idsLength, TwoWords))
                          // Update the offset for the data array in memory.
                          mstore(
                              BatchTransfer1155Params_data_head_ptr,
                              add(
                                  BatchTransfer1155Params_ids_length_offset,
                                  idsAndAmountsSize
                              )
                          )
                          // Set the length of the data array in memory to zero.
                          mstore(
                              add(
                                  BatchTransfer1155Params_data_length_basePtr,
                                  idsAndAmountsSize
                              ),
                              0
                          )
                          // Determine the total calldata size for the call to transfer.
                          let transferDataSize := add(
                              BatchTransfer1155Params_calldata_baseSize,
                              idsAndAmountsSize
                          )
                          // Copy second section of calldata (including dynamic values).
                          calldatacopy(
                              BatchTransfer1155Params_ids_length_ptr,
                              add(elementPtr, ConduitBatch1155Transfer_ids_length_offset),
                              idsAndAmountsSize
                          )
                          // Perform the call to transfer 1155 tokens.
                          let success := call(
                              gas(),
                              token,
                              0,
                              ConduitBatch1155Transfer_from_offset, // Data portion start.
                              transferDataSize, // Location of the length of callData.
                              0,
                              0
                          )
                          // If the transfer reverted:
                          if iszero(success) {
                              // If it returned a message, bubble it up as long as
                              // sufficient gas remains to do so:
                              if returndatasize() {
                                  // Ensure that sufficient gas is available to copy
                                  // returndata while expanding memory where necessary.
                                  // Start by computing word size of returndata and
                                  // allocated memory. Round up to the nearest full word.
                                  let returnDataWords := div(
                                      add(returndatasize(), AlmostOneWord),
                                      OneWord
                                  )
                                  // Note: use transferDataSize in place of msize() to
                                  // work around a Yul warning that prevents accessing
                                  // msize directly when the IR pipeline is activated.
                                  // The free memory pointer is not used here because
                                  // this function does almost all memory management
                                  // manually and does not update it, and transferDataSize
                                  // should be the largest memory value used (unless a
                                  // previous batch was larger).
                                  let msizeWords := div(transferDataSize, OneWord)
                                  // Next, compute the cost of the returndatacopy.
                                  let cost := mul(CostPerWord, returnDataWords)
                                  // Then, compute cost of new memory allocation.
                                  if gt(returnDataWords, msizeWords) {
                                      cost := add(
                                          cost,
                                          add(
                                              mul(
                                                  sub(returnDataWords, msizeWords),
                                                  CostPerWord
                                              ),
                                              div(
                                                  sub(
                                                      mul(
                                                          returnDataWords,
                                                          returnDataWords
                                                      ),
                                                      mul(msizeWords, msizeWords)
                                                  ),
                                                  MemoryExpansionCoefficient
                                              )
                                          )
                                      )
                                  }
                                  // Finally, add a small constant and compare to gas
                                  // remaining; bubble up the revert data if enough gas is
                                  // still available.
                                  if lt(add(cost, ExtraGasBuffer), gas()) {
                                      // Copy returndata to memory; overwrite existing.
                                      returndatacopy(0, 0, returndatasize())
                                      // Revert with memory region containing returndata.
                                      revert(0, returndatasize())
                                  }
                              }
                              // Set the error signature.
                              mstore(
                                  0,
                                  ERC1155BatchTransferGenericFailure_error_signature
                              )
                              // Write the token.
                              mstore(ERC1155BatchTransferGenericFailure_token_ptr, token)
                              // Increase the offset to ids by 32.
                              mstore(
                                  BatchTransfer1155Params_ids_head_ptr,
                                  ERC1155BatchTransferGenericFailure_ids_offset
                              )
                              // Increase the offset to amounts by 32.
                              mstore(
                                  BatchTransfer1155Params_amounts_head_ptr,
                                  add(
                                      OneWord,
                                      mload(BatchTransfer1155Params_amounts_head_ptr)
                                  )
                              )
                              // Return modified region. The total size stays the same as
                              // `token` uses the same number of bytes as `data.length`.
                              revert(0, transferDataSize)
                          }
                      }
                      // Reset the free memory pointer to the default value; memory must
                      // be assumed to be dirtied and not reused from this point forward.
                      // Also note that the zero slot is not reset to zero, meaning empty
                      // arrays cannot be safely created or utilized until it is restored.
                      mstore(FreeMemoryPointerSlot, DefaultFreeMemoryPointer)
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          // error ChannelClosed(address channel)
          uint256 constant ChannelClosed_error_signature = (
              0x93daadf200000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ChannelClosed_error_ptr = 0x00;
          uint256 constant ChannelClosed_channel_ptr = 0x4;
          uint256 constant ChannelClosed_error_length = 0x24;
          // For the mapping:
          // mapping(address => bool) channels
          // The position in storage for a particular account is:
          // keccak256(abi.encode(account, channels.slot))
          uint256 constant ChannelKey_channel_ptr = 0x00;
          uint256 constant ChannelKey_slot_ptr = 0x20;
          uint256 constant ChannelKey_length = 0x40;
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          /*
           * -------------------------- Disambiguation & Other Notes ---------------------
           *    - The term "head" is used as it is in the documentation for ABI encoding,
           *      but only in reference to dynamic types, i.e. it always refers to the
           *      offset or pointer to the body of a dynamic type. In calldata, the head
           *      is always an offset (relative to the parent object), while in memory,
           *      the head is always the pointer to the body. More information found here:
           *      https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding
           *        - Note that the length of an array is separate from and precedes the
           *          head of the array.
           *
           *    - The term "body" is used in place of the term "head" used in the ABI
           *      documentation. It refers to the start of the data for a dynamic type,
           *      e.g. the first word of a struct or the first word of the first element
           *      in an array.
           *
           *    - The term "pointer" is used to describe the absolute position of a value
           *      and never an offset relative to another value.
           *        - The suffix "_ptr" refers to a memory pointer.
           *        - The suffix "_cdPtr" refers to a calldata pointer.
           *
           *    - The term "offset" is used to describe the position of a value relative
           *      to some parent value. For example, OrderParameters_conduit_offset is the
           *      offset to the "conduit" value in the OrderParameters struct relative to
           *      the start of the body.
           *        - Note: Offsets are used to derive pointers.
           *
           *    - Some structs have pointers defined for all of their fields in this file.
           *      Lines which are commented out are fields that are not used in the
           *      codebase but have been left in for readability.
           */
          uint256 constant AlmostOneWord = 0x1f;
          uint256 constant OneWord = 0x20;
          uint256 constant TwoWords = 0x40;
          uint256 constant ThreeWords = 0x60;
          uint256 constant FreeMemoryPointerSlot = 0x40;
          uint256 constant ZeroSlot = 0x60;
          uint256 constant DefaultFreeMemoryPointer = 0x80;
          uint256 constant Slot0x80 = 0x80;
          uint256 constant Slot0xA0 = 0xa0;
          uint256 constant Slot0xC0 = 0xc0;
          // abi.encodeWithSignature("transferFrom(address,address,uint256)")
          uint256 constant ERC20_transferFrom_signature = (
              0x23b872dd00000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC20_transferFrom_sig_ptr = 0x0;
          uint256 constant ERC20_transferFrom_from_ptr = 0x04;
          uint256 constant ERC20_transferFrom_to_ptr = 0x24;
          uint256 constant ERC20_transferFrom_amount_ptr = 0x44;
          uint256 constant ERC20_transferFrom_length = 0x64; // 4 + 32 * 3 == 100
          // abi.encodeWithSignature(
          //     "safeTransferFrom(address,address,uint256,uint256,bytes)"
          // )
          uint256 constant ERC1155_safeTransferFrom_signature = (
              0xf242432a00000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC1155_safeTransferFrom_sig_ptr = 0x0;
          uint256 constant ERC1155_safeTransferFrom_from_ptr = 0x04;
          uint256 constant ERC1155_safeTransferFrom_to_ptr = 0x24;
          uint256 constant ERC1155_safeTransferFrom_id_ptr = 0x44;
          uint256 constant ERC1155_safeTransferFrom_amount_ptr = 0x64;
          uint256 constant ERC1155_safeTransferFrom_data_offset_ptr = 0x84;
          uint256 constant ERC1155_safeTransferFrom_data_length_ptr = 0xa4;
          uint256 constant ERC1155_safeTransferFrom_length = 0xc4; // 4 + 32 * 6 == 196
          uint256 constant ERC1155_safeTransferFrom_data_length_offset = 0xa0;
          // abi.encodeWithSignature(
          //     "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"
          // )
          uint256 constant ERC1155_safeBatchTransferFrom_signature = (
              0x2eb2c2d600000000000000000000000000000000000000000000000000000000
          );
          bytes4 constant ERC1155_safeBatchTransferFrom_selector = bytes4(
              bytes32(ERC1155_safeBatchTransferFrom_signature)
          );
          uint256 constant ERC721_transferFrom_signature = ERC20_transferFrom_signature;
          uint256 constant ERC721_transferFrom_sig_ptr = 0x0;
          uint256 constant ERC721_transferFrom_from_ptr = 0x04;
          uint256 constant ERC721_transferFrom_to_ptr = 0x24;
          uint256 constant ERC721_transferFrom_id_ptr = 0x44;
          uint256 constant ERC721_transferFrom_length = 0x64; // 4 + 32 * 3 == 100
          // abi.encodeWithSignature("NoContract(address)")
          uint256 constant NoContract_error_signature = (
              0x5f15d67200000000000000000000000000000000000000000000000000000000
          );
          uint256 constant NoContract_error_sig_ptr = 0x0;
          uint256 constant NoContract_error_token_ptr = 0x4;
          uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36
          // abi.encodeWithSignature(
          //     "TokenTransferGenericFailure(address,address,address,uint256,uint256)"
          // )
          uint256 constant TokenTransferGenericFailure_error_signature = (
              0xf486bc8700000000000000000000000000000000000000000000000000000000
          );
          uint256 constant TokenTransferGenericFailure_error_sig_ptr = 0x0;
          uint256 constant TokenTransferGenericFailure_error_token_ptr = 0x4;
          uint256 constant TokenTransferGenericFailure_error_from_ptr = 0x24;
          uint256 constant TokenTransferGenericFailure_error_to_ptr = 0x44;
          uint256 constant TokenTransferGenericFailure_error_id_ptr = 0x64;
          uint256 constant TokenTransferGenericFailure_error_amount_ptr = 0x84;
          // 4 + 32 * 5 == 164
          uint256 constant TokenTransferGenericFailure_error_length = 0xa4;
          // abi.encodeWithSignature(
          //     "BadReturnValueFromERC20OnTransfer(address,address,address,uint256)"
          // )
          uint256 constant BadReturnValueFromERC20OnTransfer_error_signature = (
              0x9889192300000000000000000000000000000000000000000000000000000000
          );
          uint256 constant BadReturnValueFromERC20OnTransfer_error_sig_ptr = 0x0;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_token_ptr = 0x4;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_from_ptr = 0x24;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_to_ptr = 0x44;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_amount_ptr = 0x64;
          // 4 + 32 * 4 == 132
          uint256 constant BadReturnValueFromERC20OnTransfer_error_length = 0x84;
          uint256 constant ExtraGasBuffer = 0x20;
          uint256 constant CostPerWord = 3;
          uint256 constant MemoryExpansionCoefficient = 0x200;
          // Values are offset by 32 bytes in order to write the token to the beginning
          // in the event of a revert
          uint256 constant BatchTransfer1155Params_ptr = 0x24;
          uint256 constant BatchTransfer1155Params_ids_head_ptr = 0x64;
          uint256 constant BatchTransfer1155Params_amounts_head_ptr = 0x84;
          uint256 constant BatchTransfer1155Params_data_head_ptr = 0xa4;
          uint256 constant BatchTransfer1155Params_data_length_basePtr = 0xc4;
          uint256 constant BatchTransfer1155Params_calldata_baseSize = 0xc4;
          uint256 constant BatchTransfer1155Params_ids_length_ptr = 0xc4;
          uint256 constant BatchTransfer1155Params_ids_length_offset = 0xa0;
          uint256 constant BatchTransfer1155Params_amounts_length_baseOffset = 0xc0;
          uint256 constant BatchTransfer1155Params_data_length_baseOffset = 0xe0;
          uint256 constant ConduitBatch1155Transfer_usable_head_size = 0x80;
          uint256 constant ConduitBatch1155Transfer_from_offset = 0x20;
          uint256 constant ConduitBatch1155Transfer_ids_head_offset = 0x60;
          uint256 constant ConduitBatch1155Transfer_amounts_head_offset = 0x80;
          uint256 constant ConduitBatch1155Transfer_ids_length_offset = 0xa0;
          uint256 constant ConduitBatch1155Transfer_amounts_length_baseOffset = 0xc0;
          uint256 constant ConduitBatch1155Transfer_calldata_baseSize = 0xc0;
          // Note: abbreviated version of above constant to adhere to line length limit.
          uint256 constant ConduitBatchTransfer_amounts_head_offset = 0x80;
          uint256 constant Invalid1155BatchTransferEncoding_ptr = 0x00;
          uint256 constant Invalid1155BatchTransferEncoding_length = 0x04;
          uint256 constant Invalid1155BatchTransferEncoding_selector = (
              0xeba2084c00000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC1155BatchTransferGenericFailure_error_signature = (
              0xafc445e200000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC1155BatchTransferGenericFailure_token_ptr = 0x04;
          uint256 constant ERC1155BatchTransferGenericFailure_ids_offset = 0xc0;
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.7;
          /**
           * @title TokenTransferrerErrors
           */
          interface TokenTransferrerErrors {
              /**
               * @dev Revert with an error when an ERC721 transfer with amount other than
               *      one is attempted.
               */
              error InvalidERC721TransferAmount();
              /**
               * @dev Revert with an error when attempting to fulfill an order where an
               *      item has an amount of zero.
               */
              error MissingItemAmount();
              /**
               * @dev Revert with an error when attempting to fulfill an order where an
               *      item has unused parameters. This includes both the token and the
               *      identifier parameters for native transfers as well as the identifier
               *      parameter for ERC20 transfers. Note that the conduit does not
               *      perform this check, leaving it up to the calling channel to enforce
               *      when desired.
               */
              error UnusedItemParameters();
              /**
               * @dev Revert with an error when an ERC20, ERC721, or ERC1155 token
               *      transfer reverts.
               *
               * @param token      The token for which the transfer was attempted.
               * @param from       The source of the attempted transfer.
               * @param to         The recipient of the attempted transfer.
               * @param identifier The identifier for the attempted transfer.
               * @param amount     The amount for the attempted transfer.
               */
              error TokenTransferGenericFailure(
                  address token,
                  address from,
                  address to,
                  uint256 identifier,
                  uint256 amount
              );
              /**
               * @dev Revert with an error when a batch ERC1155 token transfer reverts.
               *
               * @param token       The token for which the transfer was attempted.
               * @param from        The source of the attempted transfer.
               * @param to          The recipient of the attempted transfer.
               * @param identifiers The identifiers for the attempted transfer.
               * @param amounts     The amounts for the attempted transfer.
               */
              error ERC1155BatchTransferGenericFailure(
                  address token,
                  address from,
                  address to,
                  uint256[] identifiers,
                  uint256[] amounts
              );
              /**
               * @dev Revert with an error when an ERC20 token transfer returns a falsey
               *      value.
               *
               * @param token      The token for which the ERC20 transfer was attempted.
               * @param from       The source of the attempted ERC20 transfer.
               * @param to         The recipient of the attempted ERC20 transfer.
               * @param amount     The amount for the attempted ERC20 transfer.
               */
              error BadReturnValueFromERC20OnTransfer(
                  address token,
                  address from,
                  address to,
                  uint256 amount
              );
              /**
               * @dev Revert with an error when an account being called as an assumed
               *      contract does not have code and returns no data.
               *
               * @param account The account that should contain code.
               */
              error NoContract(address account);
              /**
               * @dev Revert with an error when attempting to execute an 1155 batch
               *      transfer using calldata not produced by default ABI encoding or with
               *      different lengths for ids and amounts arrays.
               */
              error Invalid1155BatchTransferEncoding();
          }
          

          File 2 of 6: DenizensOfZenAcademy
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          // 
          // ██████╗ ███████╗███╗   ██╗██╗███████╗███████╗███╗   ██╗███████╗     ██████╗ ███████╗    ███████╗███████╗███╗   ██╗ █████╗  ██████╗ █████╗ ██████╗ ███████╗███╗   ███╗██╗   ██╗
          // ██╔══██╗██╔════╝████╗  ██║██║╚══███╔╝██╔════╝████╗  ██║██╔════╝    ██╔═══██╗██╔════╝    ╚══███╔╝██╔════╝████╗  ██║██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔════╝████╗ ████║╚██╗ ██╔╝
          // ██║  ██║█████╗  ██╔██╗ ██║██║  ███╔╝ █████╗  ██╔██╗ ██║███████╗    ██║   ██║█████╗        ███╔╝ █████╗  ██╔██╗ ██║███████║██║     ███████║██║  ██║█████╗  ██╔████╔██║ ╚████╔╝ 
          // ██║  ██║██╔══╝  ██║╚██╗██║██║ ███╔╝  ██╔══╝  ██║╚██╗██║╚════██║    ██║   ██║██╔══╝       ███╔╝  ██╔══╝  ██║╚██╗██║██╔══██║██║     ██╔══██║██║  ██║██╔══╝  ██║╚██╔╝██║  ╚██╔╝  
          // ██████╔╝███████╗██║ ╚████║██║███████╗███████╗██║ ╚████║███████║    ╚██████╔╝██║         ███████╗███████╗██║ ╚████║██║  ██║╚██████╗██║  ██║██████╔╝███████╗██║ ╚═╝ ██║   ██║   
          // ╚═════╝ ╚══════╝╚═╝  ╚═══╝╚═╝╚══════╝╚══════╝╚═╝  ╚═══╝╚══════╝     ╚═════╝ ╚═╝         ╚══════╝╚══════╝╚═╝  ╚═══╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝╚═════╝ ╚══════╝╚═╝     ╚═╝   ╚═╝   
          //                                                                                                                                                                               
          // Denizens of ZenAcademy - generated with HeyMint.xyz Launchpad - https://nft-launchpad.heymint.xyz
          // 
          import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
          import {IAddressRelay} from "./interfaces/IAddressRelay.sol";
          import {BaseConfig} from "./libraries/HeyMintStorage.sol";
          contract DenizensOfZenAcademy {
              bytes32 internal constant _IMPLEMENTATION_SLOT =
                  0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
              bytes32 internal constant _ADDRESS_RELAY_SLOT =
                  keccak256("heymint.launchpad.addressRelay");
              /**
               * @notice Initializes the child contract with the base implementation address and the configuration settings
               * @param _name The name of the NFT
               * @param _symbol The symbol of the NFT
               * @param _baseConfig Base configuration settings
               */
              constructor(
                  string memory _name,
                  string memory _symbol,
                  address _addressRelay,
                  address _implementation,
                  BaseConfig memory _baseConfig
              ) {
                  StorageSlot
                      .getAddressSlot(_IMPLEMENTATION_SLOT)
                      .value = _implementation;
                  StorageSlot.getAddressSlot(_ADDRESS_RELAY_SLOT).value = _addressRelay;
                  IAddressRelay addressRelay = IAddressRelay(
                      StorageSlot.getAddressSlot(_ADDRESS_RELAY_SLOT).value
                  );
                  address implContract = addressRelay.fallbackImplAddress();
                  (bool success, ) = implContract.delegatecall(
                      abi.encodeWithSelector(0x35a825b0, _name, _symbol, _baseConfig)
                  );
                  require(success);
              }
              /**
               * @dev Delegates the current call to nftImplementation
               *
               * This function does not return to its internal call site - it will return directly to the external caller.
               */
              fallback() external payable {
                  IAddressRelay addressRelay = IAddressRelay(
                      StorageSlot.getAddressSlot(_ADDRESS_RELAY_SLOT).value
                  );
                  address implContract = addressRelay.getImplAddress(msg.sig);
                  assembly {
                      calldatacopy(0, 0, calldatasize())
                      let result := delegatecall(
                          gas(),
                          implContract,
                          0,
                          calldatasize(),
                          0,
                          0
                      )
                      returndatacopy(0, 0, returndatasize())
                      switch result
                      case 0 {
                          revert(0, returndatasize())
                      }
                      default {
                          return(0, returndatasize())
                      }
                  }
              }
              receive() external payable {}
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/ContextUpgradeable.sol";
          import "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              function __Ownable_init() internal onlyInitializing {
                  __Ownable_init_unchained();
              }
              function __Ownable_init_unchained() internal onlyInitializing {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
          pragma solidity ^0.8.0;
          import "../utils/introspection/IERC165Upgradeable.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 IERC2981Upgradeable is IERC165Upgradeable {
              /**
               * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
               * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
               */
              function royaltyInfo(uint256 tokenId, uint256 salePrice)
                  external
                  view
                  returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.1) (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 Returns the highest version that has been initialized. See {reinitializer}.
               */
              function _getInitializedVersion() internal view returns (uint8) {
                  return _initialized;
              }
              /**
               * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
               */
              function _isInitializing() internal view returns (bool) {
                  return _initializing;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/ContextUpgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              function __Pausable_init() internal onlyInitializing {
                  __Pausable_init_unchained();
              }
              function __Pausable_init_unchained() internal onlyInitializing {
                  _paused = false;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  _requireNotPaused();
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  _requirePaused();
                  _;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Throws if the contract is paused.
               */
              function _requireNotPaused() internal view virtual {
                  require(!paused(), "Pausable: paused");
              }
              /**
               * @dev Throws if the contract is not paused.
               */
              function _requirePaused() internal view virtual {
                  require(paused(), "Pausable: not paused");
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (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 This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract ContextUpgradeable is Initializable {
              function __Context_init() internal onlyInitializing {
              }
              function __Context_init_unchained() internal onlyInitializing {
              }
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
          pragma solidity ^0.8.0;
          import "../StringsUpgradeable.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 ECDSAUpgradeable {
              enum RecoverError {
                  NoError,
                  InvalidSignature,
                  InvalidSignatureLength,
                  InvalidSignatureS,
                  InvalidSignatureV // Deprecated in v4.8
              }
              function _throwError(RecoverError error) private pure {
                  if (error == RecoverError.NoError) {
                      return; // no error: do nothing
                  } else if (error == RecoverError.InvalidSignature) {
                      revert("ECDSA: invalid signature");
                  } else if (error == RecoverError.InvalidSignatureLength) {
                      revert("ECDSA: invalid signature length");
                  } else if (error == RecoverError.InvalidSignatureS) {
                      revert("ECDSA: invalid signature 's' value");
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature` or error string. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               *
               * Documentation for signature generation:
               * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
               * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
               *
               * _Available since v4.3._
               */
              function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                  if (signature.length == 65) {
                      bytes32 r;
                      bytes32 s;
                      uint8 v;
                      // ecrecover takes the signature parameters, and the only way to get them
                      // currently is to use assembly.
                      /// @solidity memory-safe-assembly
                      assembly {
                          r := mload(add(signature, 0x20))
                          s := mload(add(signature, 0x40))
                          v := byte(0, mload(add(signature, 0x60)))
                      }
                      return tryRecover(hash, v, r, s);
                  } else {
                      return (address(0), RecoverError.InvalidSignatureLength);
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature`. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               */
              function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, signature);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
               *
               * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address, RecoverError) {
                  bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                  uint8 v = uint8((uint256(vs) >> 255) + 27);
                  return tryRecover(hash, v, r, s);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
               *
               * _Available since v4.2._
               */
              function recover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, r, vs);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
               * `r` and `s` signature fields separately.
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address, RecoverError) {
                  // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                  // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                  // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                  // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                  //
                  // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                  // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                  // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                  // these malleable signatures as well.
                  if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                      return (address(0), RecoverError.InvalidSignatureS);
                  }
                  // If the signature is valid (and not malleable), return the signer address
                  address signer = ecrecover(hash, v, r, s);
                  if (signer == address(0)) {
                      return (address(0), RecoverError.InvalidSignature);
                  }
                  return (signer, RecoverError.NoError);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `v`,
               * `r` and `s` signature fields separately.
               */
              function recover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from a `hash`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                  // 32 is the length in bytes of hash,
                  // enforced by the type signature above
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          32", hash));
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from `s`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          ", StringsUpgradeable.toString(s.length), s));
              }
              /**
               * @dev Returns an Ethereum Signed Typed Data, created from a
               * `domainSeparator` and a `structHash`. This produces hash corresponding
               * to the one signed with the
               * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
               * JSON-RPC method as part of EIP-712.
               *
               * See {recover}.
               */
              function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev These functions deal with verification of Merkle Tree proofs.
           *
           * The tree and the proofs can be generated using our
           * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
           * You will find a quickstart guide in the readme.
           *
           * WARNING: You should avoid using leaf values that are 64 bytes long prior to
           * hashing, or use a hash function other than keccak256 for hashing leaves.
           * This is because the concatenation of a sorted pair of internal nodes in
           * the merkle tree could be reinterpreted as a leaf value.
           * OpenZeppelin's JavaScript library generates merkle trees that are safe
           * against this attack out of the box.
           */
          library MerkleProofUpgradeable {
              /**
               * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
               * defined by `root`. For this, a `proof` must be provided, containing
               * sibling hashes on the branch from the leaf to the root of the tree. Each
               * pair of leaves and each pair of pre-images are assumed to be sorted.
               */
              function verify(
                  bytes32[] memory proof,
                  bytes32 root,
                  bytes32 leaf
              ) internal pure returns (bool) {
                  return processProof(proof, leaf) == root;
              }
              /**
               * @dev Calldata version of {verify}
               *
               * _Available since v4.7._
               */
              function verifyCalldata(
                  bytes32[] calldata proof,
                  bytes32 root,
                  bytes32 leaf
              ) internal pure returns (bool) {
                  return processProofCalldata(proof, leaf) == root;
              }
              /**
               * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
               * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
               * hash matches the root of the tree. When processing the proof, the pairs
               * of leafs & pre-images are assumed to be sorted.
               *
               * _Available since v4.4._
               */
              function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
                  bytes32 computedHash = leaf;
                  for (uint256 i = 0; i < proof.length; i++) {
                      computedHash = _hashPair(computedHash, proof[i]);
                  }
                  return computedHash;
              }
              /**
               * @dev Calldata version of {processProof}
               *
               * _Available since v4.7._
               */
              function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
                  bytes32 computedHash = leaf;
                  for (uint256 i = 0; i < proof.length; i++) {
                      computedHash = _hashPair(computedHash, proof[i]);
                  }
                  return computedHash;
              }
              /**
               * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
               * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
               *
               * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
               *
               * _Available since v4.7._
               */
              function multiProofVerify(
                  bytes32[] memory proof,
                  bool[] memory proofFlags,
                  bytes32 root,
                  bytes32[] memory leaves
              ) internal pure returns (bool) {
                  return processMultiProof(proof, proofFlags, leaves) == root;
              }
              /**
               * @dev Calldata version of {multiProofVerify}
               *
               * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
               *
               * _Available since v4.7._
               */
              function multiProofVerifyCalldata(
                  bytes32[] calldata proof,
                  bool[] calldata proofFlags,
                  bytes32 root,
                  bytes32[] memory leaves
              ) internal pure returns (bool) {
                  return processMultiProofCalldata(proof, proofFlags, leaves) == root;
              }
              /**
               * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
               * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
               * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
               * respectively.
               *
               * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
               * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
               * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
               *
               * _Available since v4.7._
               */
              function processMultiProof(
                  bytes32[] memory proof,
                  bool[] memory proofFlags,
                  bytes32[] memory leaves
              ) internal pure returns (bytes32 merkleRoot) {
                  // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
                  // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
                  // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
                  // the merkle tree.
                  uint256 leavesLen = leaves.length;
                  uint256 totalHashes = proofFlags.length;
                  // Check proof validity.
                  require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
                  // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
                  // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
                  bytes32[] memory hashes = new bytes32[](totalHashes);
                  uint256 leafPos = 0;
                  uint256 hashPos = 0;
                  uint256 proofPos = 0;
                  // At each step, we compute the next hash using two values:
                  // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
                  //   get the next hash.
                  // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
                  //   `proof` array.
                  for (uint256 i = 0; i < totalHashes; i++) {
                      bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
                      bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
                      hashes[i] = _hashPair(a, b);
                  }
                  if (totalHashes > 0) {
                      return hashes[totalHashes - 1];
                  } else if (leavesLen > 0) {
                      return leaves[0];
                  } else {
                      return proof[0];
                  }
              }
              /**
               * @dev Calldata version of {processMultiProof}.
               *
               * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
               *
               * _Available since v4.7._
               */
              function processMultiProofCalldata(
                  bytes32[] calldata proof,
                  bool[] calldata proofFlags,
                  bytes32[] memory leaves
              ) internal pure returns (bytes32 merkleRoot) {
                  // This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
                  // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
                  // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
                  // the merkle tree.
                  uint256 leavesLen = leaves.length;
                  uint256 totalHashes = proofFlags.length;
                  // Check proof validity.
                  require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
                  // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
                  // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
                  bytes32[] memory hashes = new bytes32[](totalHashes);
                  uint256 leafPos = 0;
                  uint256 hashPos = 0;
                  uint256 proofPos = 0;
                  // At each step, we compute the next hash using two values:
                  // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
                  //   get the next hash.
                  // - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
                  //   `proof` array.
                  for (uint256 i = 0; i < totalHashes; i++) {
                      bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
                      bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
                      hashes[i] = _hashPair(a, b);
                  }
                  if (totalHashes > 0) {
                      return hashes[totalHashes - 1];
                  } else if (leavesLen > 0) {
                      return leaves[0];
                  } else {
                      return proof[0];
                  }
              }
              function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
                  return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
              }
              function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      mstore(0x00, a)
                      mstore(0x20, b)
                      value := keccak256(0x00, 0x40)
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165Upgradeable {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library MathUpgradeable {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/MathUpgradeable.sol";
          /**
           * @dev String operations.
           */
          library StringsUpgradeable {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = MathUpgradeable.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, MathUpgradeable.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              constructor() {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // 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
          // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract Pausable is Context {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              constructor() {
                  _paused = false;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  _requireNotPaused();
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  _requirePaused();
                  _;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Throws if the contract is paused.
               */
              function _requireNotPaused() internal view virtual {
                  require(!paused(), "Pausable: paused");
              }
              /**
               * @dev Throws if the contract is not paused.
               */
              function _requirePaused() internal view virtual {
                  require(paused(), "Pausable: not paused");
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          /**
           * @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 ReentrancyGuard {
              // 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;
              constructor() {
                  _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;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/common/ERC2981.sol)
          pragma solidity ^0.8.0;
          import "../../interfaces/IERC2981.sol";
          import "../../utils/introspection/ERC165.sol";
          /**
           * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.
           *
           * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for
           * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first.
           *
           * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the
           * fee is specified in basis points by default.
           *
           * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See
           * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to
           * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported.
           *
           * _Available since v4.5._
           */
          abstract contract ERC2981 is IERC2981, ERC165 {
              struct RoyaltyInfo {
                  address receiver;
                  uint96 royaltyFraction;
              }
              RoyaltyInfo private _defaultRoyaltyInfo;
              mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
                  return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @inheritdoc IERC2981
               */
              function royaltyInfo(uint256 _tokenId, uint256 _salePrice) public view virtual override returns (address, uint256) {
                  RoyaltyInfo memory royalty = _tokenRoyaltyInfo[_tokenId];
                  if (royalty.receiver == address(0)) {
                      royalty = _defaultRoyaltyInfo;
                  }
                  uint256 royaltyAmount = (_salePrice * royalty.royaltyFraction) / _feeDenominator();
                  return (royalty.receiver, royaltyAmount);
              }
              /**
               * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a
               * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an
               * override.
               */
              function _feeDenominator() internal pure virtual returns (uint96) {
                  return 10000;
              }
              /**
               * @dev Sets the royalty information that all ids in this contract will default to.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: invalid receiver");
                  _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Removes default royalty information.
               */
              function _deleteDefaultRoyalty() internal virtual {
                  delete _defaultRoyaltyInfo;
              }
              /**
               * @dev Sets the royalty information for a specific token id, overriding the global default.
               *
               * Requirements:
               *
               * - `receiver` cannot be the zero address.
               * - `feeNumerator` cannot be greater than the fee denominator.
               */
              function _setTokenRoyalty(
                  uint256 tokenId,
                  address receiver,
                  uint96 feeNumerator
              ) internal virtual {
                  require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
                  require(receiver != address(0), "ERC2981: Invalid parameters");
                  _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
              }
              /**
               * @dev Resets royalty information for the token id back to the global default.
               */
              function _resetTokenRoyalty(uint256 tokenId) internal virtual {
                  delete _tokenRoyaltyInfo[tokenId];
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC1155/ERC1155.sol)
          pragma solidity ^0.8.0;
          import "./IERC1155.sol";
          import "./IERC1155Receiver.sol";
          import "./extensions/IERC1155MetadataURI.sol";
          import "../../utils/Address.sol";
          import "../../utils/Context.sol";
          import "../../utils/introspection/ERC165.sol";
          /**
           * @dev Implementation of the basic standard multi-token.
           * See https://eips.ethereum.org/EIPS/eip-1155
           * Originally based on code by Enjin: https://github.com/enjin/erc-1155
           *
           * _Available since v3.1._
           */
          contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
              using Address for address;
              // Mapping from token ID to account balances
              mapping(uint256 => mapping(address => uint256)) private _balances;
              // Mapping from account to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
              string private _uri;
              /**
               * @dev See {_setURI}.
               */
              constructor(string memory uri_) {
                  _setURI(uri_);
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
                  return
                      interfaceId == type(IERC1155).interfaceId ||
                      interfaceId == type(IERC1155MetadataURI).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC1155MetadataURI-uri}.
               *
               * This implementation returns the same URI for *all* token types. It relies
               * on the token type ID substitution mechanism
               * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
               *
               * Clients calling this function must replace the `\\{id\\}` substring with the
               * actual token type ID.
               */
              function uri(uint256) public view virtual override returns (string memory) {
                  return _uri;
              }
              /**
               * @dev See {IERC1155-balanceOf}.
               *
               * Requirements:
               *
               * - `account` cannot be the zero address.
               */
              function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
                  require(account != address(0), "ERC1155: address zero is not a valid owner");
                  return _balances[id][account];
              }
              /**
               * @dev See {IERC1155-balanceOfBatch}.
               *
               * Requirements:
               *
               * - `accounts` and `ids` must have the same length.
               */
              function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
                  public
                  view
                  virtual
                  override
                  returns (uint256[] memory)
              {
                  require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
                  uint256[] memory batchBalances = new uint256[](accounts.length);
                  for (uint256 i = 0; i < accounts.length; ++i) {
                      batchBalances[i] = balanceOf(accounts[i], ids[i]);
                  }
                  return batchBalances;
              }
              /**
               * @dev See {IERC1155-setApprovalForAll}.
               */
              function setApprovalForAll(address operator, bool approved) public virtual override {
                  _setApprovalForAll(_msgSender(), operator, approved);
              }
              /**
               * @dev See {IERC1155-isApprovedForAll}.
               */
              function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
                  return _operatorApprovals[account][operator];
              }
              /**
               * @dev See {IERC1155-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id,
                  uint256 amount,
                  bytes memory data
              ) public virtual override {
                  require(
                      from == _msgSender() || isApprovedForAll(from, _msgSender()),
                      "ERC1155: caller is not token owner or approved"
                  );
                  _safeTransferFrom(from, to, id, amount, data);
              }
              /**
               * @dev See {IERC1155-safeBatchTransferFrom}.
               */
              function safeBatchTransferFrom(
                  address from,
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) public virtual override {
                  require(
                      from == _msgSender() || isApprovedForAll(from, _msgSender()),
                      "ERC1155: caller is not token owner or approved"
                  );
                  _safeBatchTransferFrom(from, to, ids, amounts, data);
              }
              /**
               * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
               *
               * Emits a {TransferSingle} event.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `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 memory data
              ) internal virtual {
                  require(to != address(0), "ERC1155: transfer to the zero address");
                  address operator = _msgSender();
                  uint256[] memory ids = _asSingletonArray(id);
                  uint256[] memory amounts = _asSingletonArray(amount);
                  _beforeTokenTransfer(operator, from, to, ids, amounts, data);
                  uint256 fromBalance = _balances[id][from];
                  require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
                  unchecked {
                      _balances[id][from] = fromBalance - amount;
                  }
                  _balances[id][to] += amount;
                  emit TransferSingle(operator, from, to, id, amount);
                  _afterTokenTransfer(operator, from, to, ids, amounts, data);
                  _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
              }
              /**
               * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
               *
               * Emits a {TransferBatch} event.
               *
               * Requirements:
               *
               * - 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[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) internal virtual {
                  require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
                  require(to != address(0), "ERC1155: transfer to the zero address");
                  address operator = _msgSender();
                  _beforeTokenTransfer(operator, from, to, ids, amounts, data);
                  for (uint256 i = 0; i < ids.length; ++i) {
                      uint256 id = ids[i];
                      uint256 amount = amounts[i];
                      uint256 fromBalance = _balances[id][from];
                      require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
                      unchecked {
                          _balances[id][from] = fromBalance - amount;
                      }
                      _balances[id][to] += amount;
                  }
                  emit TransferBatch(operator, from, to, ids, amounts);
                  _afterTokenTransfer(operator, from, to, ids, amounts, data);
                  _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
              }
              /**
               * @dev Sets a new URI for all token types, by relying on the token type ID
               * substitution mechanism
               * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
               *
               * By this mechanism, any occurrence of the `\\{id\\}` substring in either the
               * URI or any of the amounts in the JSON file at said URI will be replaced by
               * clients with the token type ID.
               *
               * For example, the `https://token-cdn-domain/\\{id\\}.json` URI would be
               * interpreted by clients as
               * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
               * for token type ID 0x4cce0.
               *
               * See {uri}.
               *
               * Because these URIs cannot be meaningfully represented by the {URI} event,
               * this function emits no events.
               */
              function _setURI(string memory newuri) internal virtual {
                  _uri = newuri;
              }
              /**
               * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
               *
               * Emits a {TransferSingle} event.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
               * acceptance magic value.
               */
              function _mint(
                  address to,
                  uint256 id,
                  uint256 amount,
                  bytes memory data
              ) internal virtual {
                  require(to != address(0), "ERC1155: mint to the zero address");
                  address operator = _msgSender();
                  uint256[] memory ids = _asSingletonArray(id);
                  uint256[] memory amounts = _asSingletonArray(amount);
                  _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
                  _balances[id][to] += amount;
                  emit TransferSingle(operator, address(0), to, id, amount);
                  _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
                  _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
              }
              /**
               * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
               *
               * 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 _mintBatch(
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) internal virtual {
                  require(to != address(0), "ERC1155: mint to the zero address");
                  require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
                  address operator = _msgSender();
                  _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
                  for (uint256 i = 0; i < ids.length; i++) {
                      _balances[ids[i]][to] += amounts[i];
                  }
                  emit TransferBatch(operator, address(0), to, ids, amounts);
                  _afterTokenTransfer(operator, address(0), to, ids, amounts, data);
                  _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
              }
              /**
               * @dev Destroys `amount` tokens of token type `id` from `from`
               *
               * Emits a {TransferSingle} event.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `from` must have at least `amount` tokens of token type `id`.
               */
              function _burn(
                  address from,
                  uint256 id,
                  uint256 amount
              ) internal virtual {
                  require(from != address(0), "ERC1155: burn from the zero address");
                  address operator = _msgSender();
                  uint256[] memory ids = _asSingletonArray(id);
                  uint256[] memory amounts = _asSingletonArray(amount);
                  _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
                  uint256 fromBalance = _balances[id][from];
                  require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
                  unchecked {
                      _balances[id][from] = fromBalance - amount;
                  }
                  emit TransferSingle(operator, from, address(0), id, amount);
                  _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
              }
              /**
               * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
               *
               * Emits a {TransferBatch} event.
               *
               * Requirements:
               *
               * - `ids` and `amounts` must have the same length.
               */
              function _burnBatch(
                  address from,
                  uint256[] memory ids,
                  uint256[] memory amounts
              ) internal virtual {
                  require(from != address(0), "ERC1155: burn from the zero address");
                  require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
                  address operator = _msgSender();
                  _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
                  for (uint256 i = 0; i < ids.length; i++) {
                      uint256 id = ids[i];
                      uint256 amount = amounts[i];
                      uint256 fromBalance = _balances[id][from];
                      require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
                      unchecked {
                          _balances[id][from] = fromBalance - amount;
                      }
                  }
                  emit TransferBatch(operator, from, address(0), ids, amounts);
                  _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
              }
              /**
               * @dev Approve `operator` to operate on all of `owner` tokens
               *
               * Emits an {ApprovalForAll} event.
               */
              function _setApprovalForAll(
                  address owner,
                  address operator,
                  bool approved
              ) internal virtual {
                  require(owner != operator, "ERC1155: setting approval status for self");
                  _operatorApprovals[owner][operator] = approved;
                  emit ApprovalForAll(owner, operator, approved);
              }
              /**
               * @dev Hook that is called before any token transfer. This includes minting
               * and burning, as well as batched variants.
               *
               * The same hook is called on both single and batched variants. For single
               * transfers, the length of the `ids` and `amounts` arrays will be 1.
               *
               * Calling conditions (for each `id` and `amount` pair):
               *
               * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * of token type `id` will be  transferred to `to`.
               * - When `from` is zero, `amount` tokens of token type `id` will be minted
               * for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
               * will be burned.
               * - `from` and `to` are never both zero.
               * - `ids` and `amounts` have the same, non-zero length.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address operator,
                  address from,
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) internal virtual {}
              /**
               * @dev Hook that is called after any token transfer. This includes minting
               * and burning, as well as batched variants.
               *
               * The same hook is called on both single and batched variants. For single
               * transfers, the length of the `id` and `amount` arrays will be 1.
               *
               * Calling conditions (for each `id` and `amount` pair):
               *
               * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * of token type `id` will be  transferred to `to`.
               * - When `from` is zero, `amount` tokens of token type `id` will be minted
               * for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
               * will be burned.
               * - `from` and `to` are never both zero.
               * - `ids` and `amounts` have the same, non-zero length.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address operator,
                  address from,
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) internal virtual {}
              function _doSafeTransferAcceptanceCheck(
                  address operator,
                  address from,
                  address to,
                  uint256 id,
                  uint256 amount,
                  bytes memory data
              ) private {
                  if (to.isContract()) {
                      try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                          if (response != IERC1155Receiver.onERC1155Received.selector) {
                              revert("ERC1155: ERC1155Receiver rejected tokens");
                          }
                      } catch Error(string memory reason) {
                          revert(reason);
                      } catch {
                          revert("ERC1155: transfer to non-ERC1155Receiver implementer");
                      }
                  }
              }
              function _doSafeBatchTransferAcceptanceCheck(
                  address operator,
                  address from,
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) private {
                  if (to.isContract()) {
                      try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                          bytes4 response
                      ) {
                          if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                              revert("ERC1155: ERC1155Receiver rejected tokens");
                          }
                      } catch Error(string memory reason) {
                          revert(reason);
                      } catch {
                          revert("ERC1155: transfer to non-ERC1155Receiver implementer");
                      }
                  }
              }
              function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
                  uint256[] memory array = new uint256[](1);
                  array[0] = element;
                  return array;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/extensions/ERC1155Supply.sol)
          pragma solidity ^0.8.0;
          import "../ERC1155.sol";
          /**
           * @dev Extension of ERC1155 that adds tracking of total supply per id.
           *
           * Useful for scenarios where Fungible and Non-fungible tokens have to be
           * clearly identified. Note: While a totalSupply of 1 might mean the
           * corresponding is an NFT, there is no guarantees that no other token with the
           * same id are not going to be minted.
           */
          abstract contract ERC1155Supply is ERC1155 {
              mapping(uint256 => uint256) private _totalSupply;
              /**
               * @dev Total amount of tokens in with a given id.
               */
              function totalSupply(uint256 id) public view virtual returns (uint256) {
                  return _totalSupply[id];
              }
              /**
               * @dev Indicates whether any token exist with a given id, or not.
               */
              function exists(uint256 id) public view virtual returns (bool) {
                  return ERC1155Supply.totalSupply(id) > 0;
              }
              /**
               * @dev See {ERC1155-_beforeTokenTransfer}.
               */
              function _beforeTokenTransfer(
                  address operator,
                  address from,
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) internal virtual override {
                  super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
                  if (from == address(0)) {
                      for (uint256 i = 0; i < ids.length; ++i) {
                          _totalSupply[ids[i]] += amounts[i];
                      }
                  }
                  if (to == address(0)) {
                      for (uint256 i = 0; i < ids.length; ++i) {
                          uint256 id = ids[i];
                          uint256 amount = amounts[i];
                          uint256 supply = _totalSupply[id];
                          require(supply >= amount, "ERC1155: burn amount exceeds totalSupply");
                          unchecked {
                              _totalSupply[id] = supply - amount;
                          }
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)
          pragma solidity ^0.8.0;
          import "../IERC1155.sol";
          /**
           * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
           * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
           *
           * _Available since v3.1._
           */
          interface IERC1155MetadataURI is IERC1155 {
              /**
               * @dev Returns the URI for token type `id`.
               *
               * If the `\\{id\\}` substring is present in the URI, it must be replaced by
               * clients with the actual token type ID.
               */
              function uri(uint256 id) external view returns (string memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC1155 compliant contract, as defined in the
           * https://eips.ethereum.org/EIPS/eip-1155[EIP].
           *
           * _Available since v3.1._
           */
          interface IERC1155 is IERC165 {
              /**
               * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
               */
              event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
              /**
               * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
               * transfers.
               */
              event TransferBatch(
                  address indexed operator,
                  address indexed from,
                  address indexed to,
                  uint256[] ids,
                  uint256[] values
              );
              /**
               * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
               * `approved`.
               */
              event ApprovalForAll(address indexed account, address indexed operator, bool approved);
              /**
               * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
               *
               * If an {URI} event was emitted for `id`, the standard
               * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
               * returned by {IERC1155MetadataURI-uri}.
               */
              event URI(string value, uint256 indexed id);
              /**
               * @dev Returns the amount of tokens of token type `id` owned by `account`.
               *
               * Requirements:
               *
               * - `account` cannot be the zero address.
               */
              function balanceOf(address account, uint256 id) external view returns (uint256);
              /**
               * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
               *
               * Requirements:
               *
               * - `accounts` and `ids` must have the same length.
               */
              function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
                  external
                  view
                  returns (uint256[] memory);
              /**
               * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
               *
               * Emits an {ApprovalForAll} event.
               *
               * Requirements:
               *
               * - `operator` cannot be the caller.
               */
              function setApprovalForAll(address operator, bool approved) external;
              /**
               * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
               *
               * See {setApprovalForAll}.
               */
              function isApprovedForAll(address account, address operator) external view returns (bool);
              /**
               * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
               *
               * Emits a {TransferSingle} event.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
               * - `from` must have a balance of tokens of type `id` of at least `amount`.
               * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
               * acceptance magic value.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id,
                  uint256 amount,
                  bytes calldata data
              ) external;
              /**
               * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
               *
               * Emits a {TransferBatch} event.
               *
               * Requirements:
               *
               * - `ids` and `amounts` must have the same length.
               * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
               * acceptance magic value.
               */
              function safeBatchTransferFrom(
                  address from,
                  address to,
                  uint256[] calldata ids,
                  uint256[] calldata amounts,
                  bytes calldata data
              ) external;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev _Available since v3.1._
           */
          interface IERC1155Receiver is IERC165 {
              /**
               * @dev Handles the receipt of a single ERC1155 token type. This function is
               * called at the end of a `safeTransferFrom` after the balance has been updated.
               *
               * NOTE: To accept the transfer, this must return
               * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
               * (i.e. 0xf23a6e61, or its own function selector).
               *
               * @param operator The address which initiated the transfer (i.e. msg.sender)
               * @param from The address which previously owned the token
               * @param id The ID of the token being transferred
               * @param value The amount of tokens being transferred
               * @param data Additional data with no specified format
               * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
               */
              function onERC1155Received(
                  address operator,
                  address from,
                  uint256 id,
                  uint256 value,
                  bytes calldata data
              ) external returns (bytes4);
              /**
               * @dev Handles the receipt of a multiple ERC1155 token types. This function
               * is called at the end of a `safeBatchTransferFrom` after the balances have
               * been updated.
               *
               * NOTE: To accept the transfer(s), this must return
               * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
               * (i.e. 0xbc197c81, or its own function selector).
               *
               * @param operator The address which initiated the batch transfer (i.e. msg.sender)
               * @param from The address which previously owned the token
               * @param ids An array containing ids of each token being transferred (order and length must match values array)
               * @param values An array containing amounts of each token being transferred (order and length must match ids array)
               * @param data Additional data with no specified format
               * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
               */
              function onERC1155BatchReceived(
                  address operator,
                  address from,
                  uint256[] calldata ids,
                  uint256[] calldata values,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/ERC721.sol)
          pragma solidity ^0.8.0;
          import "./IERC721.sol";
          import "./IERC721Receiver.sol";
          import "./extensions/IERC721Metadata.sol";
          import "../../utils/Address.sol";
          import "../../utils/Context.sol";
          import "../../utils/Strings.sol";
          import "../../utils/introspection/ERC165.sol";
          /**
           * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
           * the Metadata extension, but not including the Enumerable extension, which is available separately as
           * {ERC721Enumerable}.
           */
          contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
              using Address for address;
              using Strings for uint256;
              // Token name
              string private _name;
              // Token symbol
              string private _symbol;
              // Mapping from token ID to owner address
              mapping(uint256 => address) private _owners;
              // Mapping owner address to token count
              mapping(address => uint256) private _balances;
              // Mapping from token ID to approved address
              mapping(uint256 => address) private _tokenApprovals;
              // Mapping from owner to operator approvals
              mapping(address => mapping(address => bool)) private _operatorApprovals;
              /**
               * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
                  return
                      interfaceId == type(IERC721).interfaceId ||
                      interfaceId == type(IERC721Metadata).interfaceId ||
                      super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC721-balanceOf}.
               */
              function balanceOf(address owner) public view virtual override returns (uint256) {
                  require(owner != address(0), "ERC721: address zero is not a valid owner");
                  return _balances[owner];
              }
              /**
               * @dev See {IERC721-ownerOf}.
               */
              function ownerOf(uint256 tokenId) public view virtual override returns (address) {
                  address owner = _ownerOf(tokenId);
                  require(owner != address(0), "ERC721: invalid token ID");
                  return owner;
              }
              /**
               * @dev See {IERC721Metadata-name}.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev See {IERC721Metadata-symbol}.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev See {IERC721Metadata-tokenURI}.
               */
              function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
                  _requireMinted(tokenId);
                  string memory baseURI = _baseURI();
                  return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
              }
              /**
               * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
               * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
               * by default, can be overridden in child contracts.
               */
              function _baseURI() internal view virtual returns (string memory) {
                  return "";
              }
              /**
               * @dev See {IERC721-approve}.
               */
              function approve(address to, uint256 tokenId) public virtual override {
                  address owner = ERC721.ownerOf(tokenId);
                  require(to != owner, "ERC721: approval to current owner");
                  require(
                      _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                      "ERC721: approve caller is not token owner or approved for all"
                  );
                  _approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-getApproved}.
               */
              function getApproved(uint256 tokenId) public view virtual override returns (address) {
                  _requireMinted(tokenId);
                  return _tokenApprovals[tokenId];
              }
              /**
               * @dev See {IERC721-setApprovalForAll}.
               */
              function setApprovalForAll(address operator, bool approved) public virtual override {
                  _setApprovalForAll(_msgSender(), operator, approved);
              }
              /**
               * @dev See {IERC721-isApprovedForAll}.
               */
              function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
                  return _operatorApprovals[owner][operator];
              }
              /**
               * @dev See {IERC721-transferFrom}.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  //solhint-disable-next-line max-line-length
                  require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
                  _transfer(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) public virtual override {
                  safeTransferFrom(from, to, tokenId, "");
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) public virtual override {
                  require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
                  _safeTransfer(from, to, tokenId, data);
              }
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * `data` is additional data, it has no specified format and it is sent in call to `to`.
               *
               * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
               * implement alternative mechanisms to perform token transfer, such as signature-based.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeTransfer(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _transfer(from, to, tokenId);
                  require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
              }
              /**
               * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
               */
              function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
                  return _owners[tokenId];
              }
              /**
               * @dev Returns whether `tokenId` exists.
               *
               * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
               *
               * Tokens start existing when they are minted (`_mint`),
               * and stop existing when they are burned (`_burn`).
               */
              function _exists(uint256 tokenId) internal view virtual returns (bool) {
                  return _ownerOf(tokenId) != address(0);
              }
              /**
               * @dev Returns whether `spender` is allowed to manage `tokenId`.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
                  address owner = ERC721.ownerOf(tokenId);
                  return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
              }
              /**
               * @dev Safely mints `tokenId` and transfers it to `to`.
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function _safeMint(address to, uint256 tokenId) internal virtual {
                  _safeMint(to, tokenId, "");
              }
              /**
               * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
               * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
               */
              function _safeMint(
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) internal virtual {
                  _mint(to, tokenId);
                  require(
                      _checkOnERC721Received(address(0), to, tokenId, data),
                      "ERC721: transfer to non ERC721Receiver implementer"
                  );
              }
              /**
               * @dev Mints `tokenId` and transfers it to `to`.
               *
               * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
               *
               * Requirements:
               *
               * - `tokenId` must not exist.
               * - `to` cannot be the zero address.
               *
               * Emits a {Transfer} event.
               */
              function _mint(address to, uint256 tokenId) internal virtual {
                  require(to != address(0), "ERC721: mint to the zero address");
                  require(!_exists(tokenId), "ERC721: token already minted");
                  _beforeTokenTransfer(address(0), to, tokenId, 1);
                  // Check that tokenId was not minted by `_beforeTokenTransfer` hook
                  require(!_exists(tokenId), "ERC721: token already minted");
                  unchecked {
                      // Will not overflow unless all 2**256 token ids are minted to the same owner.
                      // Given that tokens are minted one by one, it is impossible in practice that
                      // this ever happens. Might change if we allow batch minting.
                      // The ERC fails to describe this case.
                      _balances[to] += 1;
                  }
                  _owners[tokenId] = to;
                  emit Transfer(address(0), to, tokenId);
                  _afterTokenTransfer(address(0), to, tokenId, 1);
              }
              /**
               * @dev Destroys `tokenId`.
               * The approval is cleared when the token is burned.
               * This is an internal function that does not check if the sender is authorized to operate on the token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               *
               * Emits a {Transfer} event.
               */
              function _burn(uint256 tokenId) internal virtual {
                  address owner = ERC721.ownerOf(tokenId);
                  _beforeTokenTransfer(owner, address(0), tokenId, 1);
                  // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
                  owner = ERC721.ownerOf(tokenId);
                  // Clear approvals
                  delete _tokenApprovals[tokenId];
                  unchecked {
                      // Cannot overflow, as that would require more tokens to be burned/transferred
                      // out than the owner initially received through minting and transferring in.
                      _balances[owner] -= 1;
                  }
                  delete _owners[tokenId];
                  emit Transfer(owner, address(0), tokenId);
                  _afterTokenTransfer(owner, address(0), tokenId, 1);
              }
              /**
               * @dev Transfers `tokenId` from `from` to `to`.
               *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
               *
               * Requirements:
               *
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               *
               * Emits a {Transfer} event.
               */
              function _transfer(
                  address from,
                  address to,
                  uint256 tokenId
              ) internal virtual {
                  require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
                  require(to != address(0), "ERC721: transfer to the zero address");
                  _beforeTokenTransfer(from, to, tokenId, 1);
                  // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
                  require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
                  // Clear approvals from the previous owner
                  delete _tokenApprovals[tokenId];
                  unchecked {
                      // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
                      // `from`'s balance is the number of token held, which is at least one before the current
                      // transfer.
                      // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
                      // all 2**256 token ids to be minted, which in practice is impossible.
                      _balances[from] -= 1;
                      _balances[to] += 1;
                  }
                  _owners[tokenId] = to;
                  emit Transfer(from, to, tokenId);
                  _afterTokenTransfer(from, to, tokenId, 1);
              }
              /**
               * @dev Approve `to` to operate on `tokenId`
               *
               * Emits an {Approval} event.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _tokenApprovals[tokenId] = to;
                  emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
              }
              /**
               * @dev Approve `operator` to operate on all of `owner` tokens
               *
               * Emits an {ApprovalForAll} event.
               */
              function _setApprovalForAll(
                  address owner,
                  address operator,
                  bool approved
              ) internal virtual {
                  require(owner != operator, "ERC721: approve to caller");
                  _operatorApprovals[owner][operator] = approved;
                  emit ApprovalForAll(owner, operator, approved);
              }
              /**
               * @dev Reverts if the `tokenId` has not been minted yet.
               */
              function _requireMinted(uint256 tokenId) internal view virtual {
                  require(_exists(tokenId), "ERC721: invalid token ID");
              }
              /**
               * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
               * The call is not executed if the target address is not a contract.
               *
               * @param from address representing the previous owner of the given token ID
               * @param to target address that will receive the tokens
               * @param tokenId uint256 ID of the token to be transferred
               * @param data bytes optional data to send along with the call
               * @return bool whether the call correctly returned the expected magic value
               */
              function _checkOnERC721Received(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) private returns (bool) {
                  if (to.isContract()) {
                      try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                          return retval == IERC721Receiver.onERC721Received.selector;
                      } catch (bytes memory reason) {
                          if (reason.length == 0) {
                              revert("ERC721: transfer to non ERC721Receiver implementer");
                          } else {
                              /// @solidity memory-safe-assembly
                              assembly {
                                  revert(add(32, reason), mload(reason))
                              }
                          }
                      }
                  } else {
                      return true;
                  }
              }
              /**
               * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
               * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
               * - When `from` is zero, the tokens will be minted for `to`.
               * - When `to` is zero, ``from``'s tokens will be burned.
               * - `from` and `to` are never both zero.
               * - `batchSize` is non-zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 firstTokenId,
                  uint256 batchSize
              ) internal virtual {}
              /**
               * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
               * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
               *
               * Calling conditions:
               *
               * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
               * - When `from` is zero, the tokens were minted for `to`.
               * - When `to` is zero, ``from``'s tokens were burned.
               * - `from` and `to` are never both zero.
               * - `batchSize` is non-zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 firstTokenId,
                  uint256 batchSize
              ) internal virtual {}
              /**
               * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
               *
               * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
               * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
               * that `ownerOf(tokenId)` is `a`.
               */
              // solhint-disable-next-line func-name-mixedcase
              function __unsafe_increaseBalance(address account, uint256 amount) internal {
                  _balances[account] += amount;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol)
          pragma solidity ^0.8.0;
          import "../ERC721.sol";
          import "./IERC721Enumerable.sol";
          /**
           * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
           * enumerability of all the token ids in the contract as well as all token ids owned by each
           * account.
           */
          abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
              // Mapping from owner to list of owned token IDs
              mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
              // Mapping from token ID to index of the owner tokens list
              mapping(uint256 => uint256) private _ownedTokensIndex;
              // Array with all token ids, used for enumeration
              uint256[] private _allTokens;
              // Mapping from token id to position in the allTokens array
              mapping(uint256 => uint256) private _allTokensIndex;
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
                  return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
              }
              /**
               * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
               */
              function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
                  require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
                  return _ownedTokens[owner][index];
              }
              /**
               * @dev See {IERC721Enumerable-totalSupply}.
               */
              function totalSupply() public view virtual override returns (uint256) {
                  return _allTokens.length;
              }
              /**
               * @dev See {IERC721Enumerable-tokenByIndex}.
               */
              function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
                  require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
                  return _allTokens[index];
              }
              /**
               * @dev See {ERC721-_beforeTokenTransfer}.
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 firstTokenId,
                  uint256 batchSize
              ) internal virtual override {
                  super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
                  if (batchSize > 1) {
                      // Will only trigger during construction. Batch transferring (minting) is not available afterwards.
                      revert("ERC721Enumerable: consecutive transfers not supported");
                  }
                  uint256 tokenId = firstTokenId;
                  if (from == address(0)) {
                      _addTokenToAllTokensEnumeration(tokenId);
                  } else if (from != to) {
                      _removeTokenFromOwnerEnumeration(from, tokenId);
                  }
                  if (to == address(0)) {
                      _removeTokenFromAllTokensEnumeration(tokenId);
                  } else if (to != from) {
                      _addTokenToOwnerEnumeration(to, tokenId);
                  }
              }
              /**
               * @dev Private function to add a token to this extension's ownership-tracking data structures.
               * @param to address representing the new owner of the given token ID
               * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
               */
              function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
                  uint256 length = ERC721.balanceOf(to);
                  _ownedTokens[to][length] = tokenId;
                  _ownedTokensIndex[tokenId] = length;
              }
              /**
               * @dev Private function to add a token to this extension's token tracking data structures.
               * @param tokenId uint256 ID of the token to be added to the tokens list
               */
              function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
                  _allTokensIndex[tokenId] = _allTokens.length;
                  _allTokens.push(tokenId);
              }
              /**
               * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
               * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
               * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
               * This has O(1) time complexity, but alters the order of the _ownedTokens array.
               * @param from address representing the previous owner of the given token ID
               * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
               */
              function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
                  // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
                  // then delete the last slot (swap and pop).
                  uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
                  uint256 tokenIndex = _ownedTokensIndex[tokenId];
                  // When the token to delete is the last token, the swap operation is unnecessary
                  if (tokenIndex != lastTokenIndex) {
                      uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
                      _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                      _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
                  }
                  // This also deletes the contents at the last position of the array
                  delete _ownedTokensIndex[tokenId];
                  delete _ownedTokens[from][lastTokenIndex];
              }
              /**
               * @dev Private function to remove a token from this extension's token tracking data structures.
               * This has O(1) time complexity, but alters the order of the _allTokens array.
               * @param tokenId uint256 ID of the token to be removed from the tokens list
               */
              function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
                  // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
                  // then delete the last slot (swap and pop).
                  uint256 lastTokenIndex = _allTokens.length - 1;
                  uint256 tokenIndex = _allTokensIndex[tokenId];
                  // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
                  // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
                  // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
                  uint256 lastTokenId = _allTokens[lastTokenIndex];
                  _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                  _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
                  // This also deletes the contents at the last position of the array
                  delete _allTokensIndex[tokenId];
                  _allTokens.pop();
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
          pragma solidity ^0.8.0;
          import "../IERC721.sol";
          /**
           * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
           * @dev See https://eips.ethereum.org/EIPS/eip-721
           */
          interface IERC721Enumerable is IERC721 {
              /**
               * @dev Returns the total amount of tokens stored by the contract.
               */
              function totalSupply() external view returns (uint256);
              /**
               * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
               * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
               */
              function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
              /**
               * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
               * Use along with {totalSupply} to enumerate all tokens.
               */
              function tokenByIndex(uint256 index) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC721.sol";
          /**
           * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
           * @dev See https://eips.ethereum.org/EIPS/eip-721
           */
          interface IERC721Metadata is IERC721 {
              /**
               * @dev Returns the token collection name.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
               */
              function tokenURI(uint256 tokenId) external view returns (string memory);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
          pragma solidity ^0.8.0;
          import "../../utils/introspection/IERC165.sol";
          /**
           * @dev Required interface of an ERC721 compliant contract.
           */
          interface IERC721 is IERC165 {
              /**
               * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
               */
              event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
               */
              event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
              /**
               * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
               */
              event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
              /**
               * @dev Returns the number of tokens in ``owner``'s account.
               */
              function balanceOf(address owner) external view returns (uint256 balance);
              /**
               * @dev Returns the owner of the `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function ownerOf(uint256 tokenId) external view returns (address owner);
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes calldata data
              ) external;
              /**
               * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
               * are aware of the ERC721 protocol to prevent tokens from being forever locked.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must exist and be owned by `from`.
               * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
               * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
               *
               * Emits a {Transfer} event.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Transfers `tokenId` token from `from` to `to`.
               *
               * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
               * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
               * understand this adds an external call which potentially creates a reentrancy vulnerability.
               *
               * Requirements:
               *
               * - `from` cannot be the zero address.
               * - `to` cannot be the zero address.
               * - `tokenId` token must be owned by `from`.
               * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
               *
               * Emits a {Transfer} event.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external;
              /**
               * @dev Gives permission to `to` to transfer `tokenId` token to another account.
               * The approval is cleared when the token is transferred.
               *
               * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function approve(address to, uint256 tokenId) external;
              /**
               * @dev Approve or remove `operator` as an operator for the caller.
               * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
               *
               * Requirements:
               *
               * - The `operator` cannot be the caller.
               *
               * Emits an {ApprovalForAll} event.
               */
              function setApprovalForAll(address operator, bool _approved) external;
              /**
               * @dev Returns the account approved for `tokenId` token.
               *
               * Requirements:
               *
               * - `tokenId` must exist.
               */
              function getApproved(uint256 tokenId) external view returns (address operator);
              /**
               * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
               *
               * See {setApprovalForAll}
               */
              function isApprovedForAll(address owner, address operator) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
          pragma solidity ^0.8.0;
          /**
           * @title ERC721 token receiver interface
           * @dev Interface for any contract that wants to support safeTransfers
           * from ERC721 asset contracts.
           */
          interface IERC721Receiver {
              /**
               * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
               * by `operator` from `from`, this function is called.
               *
               * It must return its Solidity selector to confirm the token transfer.
               * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
               *
               * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
               */
              function onERC721Received(
                  address operator,
                  address from,
                  uint256 tokenId,
                  bytes calldata data
              ) external returns (bytes4);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract Context {
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
          pragma solidity ^0.8.0;
          /**
           * @title Counters
           * @author Matt Condon (@shrugs)
           * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
           * of elements in a mapping, issuing ERC721 ids, or counting request ids.
           *
           * Include with `using Counters for Counters.Counter;`
           */
          library Counters {
              struct Counter {
                  // This variable should never be directly accessed by users of the library: interactions must be restricted to
                  // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
                  // this feature: see https://github.com/ethereum/solidity/issues/4637
                  uint256 _value; // default: 0
              }
              function current(Counter storage counter) internal view returns (uint256) {
                  return counter._value;
              }
              function increment(Counter storage counter) internal {
                  unchecked {
                      counter._value += 1;
                  }
              }
              function decrement(Counter storage counter) internal {
                  uint256 value = counter._value;
                  require(value > 0, "Counter: decrement overflow");
                  unchecked {
                      counter._value = value - 1;
                  }
              }
              function reset(Counter storage counter) internal {
                  counter._value = 0;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
          pragma solidity ^0.8.0;
          import "../Strings.sol";
          /**
           * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
           *
           * These functions can be used to verify that a message was signed by the holder
           * of the private keys of a given address.
           */
          library ECDSA {
              enum RecoverError {
                  NoError,
                  InvalidSignature,
                  InvalidSignatureLength,
                  InvalidSignatureS,
                  InvalidSignatureV // Deprecated in v4.8
              }
              function _throwError(RecoverError error) private pure {
                  if (error == RecoverError.NoError) {
                      return; // no error: do nothing
                  } else if (error == RecoverError.InvalidSignature) {
                      revert("ECDSA: invalid signature");
                  } else if (error == RecoverError.InvalidSignatureLength) {
                      revert("ECDSA: invalid signature length");
                  } else if (error == RecoverError.InvalidSignatureS) {
                      revert("ECDSA: invalid signature 's' value");
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature` or error string. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               *
               * Documentation for signature generation:
               * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
               * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
               *
               * _Available since v4.3._
               */
              function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                  if (signature.length == 65) {
                      bytes32 r;
                      bytes32 s;
                      uint8 v;
                      // ecrecover takes the signature parameters, and the only way to get them
                      // currently is to use assembly.
                      /// @solidity memory-safe-assembly
                      assembly {
                          r := mload(add(signature, 0x20))
                          s := mload(add(signature, 0x40))
                          v := byte(0, mload(add(signature, 0x60)))
                      }
                      return tryRecover(hash, v, r, s);
                  } else {
                      return (address(0), RecoverError.InvalidSignatureLength);
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature`. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               */
              function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, signature);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
               *
               * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address, RecoverError) {
                  bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                  uint8 v = uint8((uint256(vs) >> 255) + 27);
                  return tryRecover(hash, v, r, s);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
               *
               * _Available since v4.2._
               */
              function recover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, r, vs);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
               * `r` and `s` signature fields separately.
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address, RecoverError) {
                  // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                  // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                  // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                  // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                  //
                  // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                  // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                  // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                  // these malleable signatures as well.
                  if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                      return (address(0), RecoverError.InvalidSignatureS);
                  }
                  // If the signature is valid (and not malleable), return the signer address
                  address signer = ecrecover(hash, v, r, s);
                  if (signer == address(0)) {
                      return (address(0), RecoverError.InvalidSignature);
                  }
                  return (signer, RecoverError.NoError);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `v`,
               * `r` and `s` signature fields separately.
               */
              function recover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from a `hash`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                  // 32 is the length in bytes of hash,
                  // enforced by the type signature above
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          32", hash));
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from `s`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          ", Strings.toString(s.length), s));
              }
              /**
               * @dev Returns an Ethereum Signed Typed Data, created from a
               * `domainSeparator` and a `structHash`. This produces hash corresponding
               * to the one signed with the
               * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
               * JSON-RPC method as part of EIP-712.
               *
               * See {recover}.
               */
              function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
          pragma solidity ^0.8.0;
          import "./IERC165.sol";
          /**
           * @dev Implementation of the {IERC165} interface.
           *
           * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
           * for the additional interface id that will be supported. For example:
           *
           * ```solidity
           * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
           *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
           * }
           * ```
           *
           * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
           */
          abstract contract ERC165 is IERC165 {
              /**
               * @dev See {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                  return interfaceId == type(IERC165).interfaceId;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165 {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library Math {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (utils/StorageSlot.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Library for reading and writing primitive types to specific storage slots.
           *
           * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
           * This library helps with reading and writing to such slots without the need for inline assembly.
           *
           * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
           *
           * Example usage to set ERC1967 implementation slot:
           * ```
           * contract ERC1967 {
           *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
           *
           *     function _getImplementation() internal view returns (address) {
           *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
           *     }
           *
           *     function _setImplementation(address newImplementation) internal {
           *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
           *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
           *     }
           * }
           * ```
           *
           * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
           */
          library StorageSlot {
              struct AddressSlot {
                  address value;
              }
              struct BooleanSlot {
                  bool value;
              }
              struct Bytes32Slot {
                  bytes32 value;
              }
              struct Uint256Slot {
                  uint256 value;
              }
              /**
               * @dev Returns an `AddressSlot` with member `value` located at `slot`.
               */
              function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
               */
              function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
               */
              function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      r.slot := slot
                  }
              }
              /**
               * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
               */
              function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                  /// @solidity memory-safe-assembly
                  assembly {
                      r.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/Math.sol";
          /**
           * @dev String operations.
           */
          library Strings {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = Math.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, Math.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {IAddressRelay, Implementation} from "./interfaces/IAddressRelay.sol";
          import {IERC165} from "./interfaces/IERC165.sol";
          import {IERC173} from "./interfaces/IERC173.sol";
          /**
           * @author Created by HeyMint Launchpad https://join.heymint.xyz
           * @notice This contract contains the base logic for ERC-721A tokens deployed with HeyMint
           */
          contract AddressRelay is IAddressRelay, Ownable {
              mapping(bytes4 => address) public selectorToImplAddress;
              mapping(bytes4 => bool) public supportedInterfaces;
              bytes4[] selectors;
              address[] implAddresses;
              address public fallbackImplAddress;
              bool public relayFrozen;
              constructor() {
                  supportedInterfaces[0x01ffc9a7] = true; // IERC165
                  supportedInterfaces[0x7f5828d0] = true; // IERC173
                  supportedInterfaces[0x80ac58cd] = true; // IERC721
                  supportedInterfaces[0x5b5e139f] = true; // IERC721Metadata
                  supportedInterfaces[0x2a55205a] = true; // IERC2981
                  supportedInterfaces[0xad092b5c] = true; // IERC4907
              }
              /**
               * @notice Permanently freezes the relay so no more selectors can be added or removed
               */
              function freezeRelay() external onlyOwner {
                  relayFrozen = true;
              }
              /**
               * @notice Adds or updates selectors and their implementation addresses
               * @param _selectors The selectors to add or update
               * @param _implAddress The implementation address the selectors will point to
               */
              function addOrUpdateSelectors(
                  bytes4[] memory _selectors,
                  address _implAddress
              ) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  for (uint256 i = 0; i < _selectors.length; i++) {
                      bytes4 selector = _selectors[i];
                      selectorToImplAddress[selector] = _implAddress;
                      selectors.push(selector);
                  }
                  bool implAddressExists = false;
                  for (uint256 i = 0; i < implAddresses.length; i++) {
                      if (implAddresses[i] == _implAddress) {
                          implAddressExists = true;
                          break;
                      }
                  }
                  if (!implAddressExists) {
                      implAddresses.push(_implAddress);
                  }
              }
              /**
               * @notice Removes selectors
               * @param _selectors The selectors to remove
               */
              function removeSelectors(bytes4[] memory _selectors) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  for (uint256 i = 0; i < _selectors.length; i++) {
                      bytes4 selector = _selectors[i];
                      delete selectorToImplAddress[selector];
                      for (uint256 j = 0; j < selectors.length; j++) {
                          if (selectors[j] == selector) {
                              // this just sets the value to 0, but doesn't remove it from the array
                              delete selectors[j];
                              break;
                          }
                      }
                  }
              }
              /**
               * @notice Removes an implementation address and all the selectors that point to it
               * @param _implAddress The implementation address to remove
               */
              function removeImplAddressAndAllSelectors(
                  address _implAddress
              ) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  for (uint256 i = 0; i < implAddresses.length; i++) {
                      if (implAddresses[i] == _implAddress) {
                          // this just sets the value to 0, but doesn't remove it from the array
                          delete implAddresses[i];
                          break;
                      }
                  }
                  for (uint256 i = 0; i < selectors.length; i++) {
                      if (selectorToImplAddress[selectors[i]] == _implAddress) {
                          delete selectorToImplAddress[selectors[i]];
                          delete selectors[i];
                      }
                  }
              }
              /**
               * @notice Returns the implementation address for a given function selector
               * @param _functionSelector The function selector to get the implementation address for
               */
              function getImplAddress(
                  bytes4 _functionSelector
              ) external view returns (address) {
                  address implAddress = selectorToImplAddress[_functionSelector];
                  if (implAddress == address(0)) {
                      implAddress = fallbackImplAddress;
                  }
                  require(implAddress != address(0), "Function does not exist");
                  return implAddress;
              }
              /**
               * @notice Returns the implementation address for a given function selector. Throws an error if function does not exist.
               * @param _functionSelector The function selector to get the implementation address for
               */
              function getImplAddressNoFallback(
                  bytes4 _functionSelector
              ) external view returns (address) {
                  address implAddress = selectorToImplAddress[_functionSelector];
                  require(implAddress != address(0), "Function does not exist");
                  return implAddress;
              }
              /**
               * @notice Returns all the implementation addresses and the selectors they support
               * @return impls_ An array of Implementation structs
               */
              function getAllImplAddressesAndSelectors()
                  external
                  view
                  returns (Implementation[] memory)
              {
                  uint256 trueImplAddressCount = 0;
                  uint256 implAddressesLength = implAddresses.length;
                  for (uint256 i = 0; i < implAddressesLength; i++) {
                      if (implAddresses[i] != address(0)) {
                          trueImplAddressCount++;
                      }
                  }
                  Implementation[] memory impls = new Implementation[](
                      trueImplAddressCount
                  );
                  for (uint256 i = 0; i < implAddressesLength; i++) {
                      if (implAddresses[i] == address(0)) {
                          continue;
                      }
                      address implAddress = implAddresses[i];
                      bytes4[] memory selectors_;
                      uint256 selectorCount = 0;
                      uint256 selectorsLength = selectors.length;
                      for (uint256 j = 0; j < selectorsLength; j++) {
                          if (selectorToImplAddress[selectors[j]] == implAddress) {
                              selectorCount++;
                          }
                      }
                      selectors_ = new bytes4[](selectorCount);
                      uint256 selectorIndex = 0;
                      for (uint256 j = 0; j < selectorsLength; j++) {
                          if (selectorToImplAddress[selectors[j]] == implAddress) {
                              selectors_[selectorIndex] = selectors[j];
                              selectorIndex++;
                          }
                      }
                      impls[i] = Implementation(implAddress, selectors_);
                  }
                  return impls;
              }
              /**
               * @notice Return all the function selectors associated with an implementation address
               * @param _implAddress The implementation address to get the selectors for
               */
              function getSelectorsForImplAddress(
                  address _implAddress
              ) external view returns (bytes4[] memory) {
                  uint256 selectorCount = 0;
                  uint256 selectorsLength = selectors.length;
                  for (uint256 i = 0; i < selectorsLength; i++) {
                      if (selectorToImplAddress[selectors[i]] == _implAddress) {
                          selectorCount++;
                      }
                  }
                  bytes4[] memory selectorArr = new bytes4[](selectorCount);
                  uint256 selectorIndex = 0;
                  for (uint256 i = 0; i < selectorsLength; i++) {
                      if (selectorToImplAddress[selectors[i]] == _implAddress) {
                          selectorArr[selectorIndex] = selectors[i];
                          selectorIndex++;
                      }
                  }
                  return selectorArr;
              }
              /**
               * @notice Sets the fallback implementation address to use when a function selector is not found
               * @param _fallbackAddress The fallback implementation address
               */
              function setFallbackImplAddress(
                  address _fallbackAddress
              ) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  fallbackImplAddress = _fallbackAddress;
              }
              /**
               * @notice Updates the supported interfaces
               * @param _interfaceId The interface ID to update
               * @param _supported Whether the interface is supported or not
               */
              function updateSupportedInterfaces(
                  bytes4 _interfaceId,
                  bool _supported
              ) external onlyOwner {
                  supportedInterfaces[_interfaceId] = _supported;
              }
              /**
               * @notice Returns whether the interface is supported or not
               * @param _interfaceId The interface ID to check
               */
              function supportsInterface(
                  bytes4 _interfaceId
              ) external view returns (bool) {
                  return supportedInterfaces[_interfaceId];
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {HeyMintERC721AUpgradeable} from "./HeyMintERC721AUpgradeable.sol";
          import {BaseConfig, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          import {ERC721AUpgradeable, IERC721AUpgradeable} from "erc721a-upgradeable/contracts/ERC721AUpgradeable.sol";
          import {ECDSAUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
          import {IERC2981Upgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
          contract HeyMintERC721ABase is HeyMintERC721AUpgradeable, IERC2981Upgradeable {
              using HeyMintStorage for HeyMintStorage.State;
              using ECDSAUpgradeable for bytes32;
              // Default subscription address to use to enable royalty enforcement on certain exchanges like OpenSea
              address public constant CORI_SUBSCRIPTION_ADDRESS =
                  0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
              // Default subscription address to use as a placeholder for no royalty enforcement
              address public constant EMPTY_SUBSCRIPTION_ADDRESS =
                  0x511af84166215d528ABf8bA6437ec4BEcF31934B;
              /**
               * @notice Initializes a new child deposit contract
               * @param _name The name of the token
               * @param _symbol The symbol of the token
               * @param _config Base configuration settings
               */
              function initialize(
                  string memory _name,
                  string memory _symbol,
                  BaseConfig memory _config
              ) public initializerERC721A initializer {
                  __ERC721A_init(_name, _symbol);
                  __Ownable_init();
                  __ReentrancyGuard_init();
                  __OperatorFilterer_init(
                      _config.enforceRoyalties == true
                          ? CORI_SUBSCRIPTION_ADDRESS
                          : EMPTY_SUBSCRIPTION_ADDRESS,
                      true
                  );
                  HeyMintStorage.state().cfg = _config;
                  // If public sale start time is set but end time is not, set default end time
                  if (_config.publicSaleStartTime > 0 && _config.publicSaleEndTime == 0) {
                      HeyMintStorage.state().cfg.publicSaleEndTime =
                          _config.publicSaleStartTime +
                          520 weeks;
                  }
                  // If public sale end time is set but not start time, set default start time
                  if (_config.publicSaleEndTime > 0 && _config.publicSaleStartTime == 0) {
                      HeyMintStorage.state().cfg.publicSaleStartTime = uint32(
                          block.timestamp
                      );
                  }
                  // If presale start time is set but end time is not, set default end time
                  if (_config.presaleStartTime > 0 && _config.presaleEndTime == 0) {
                      HeyMintStorage.state().cfg.presaleEndTime =
                          _config.presaleStartTime +
                          520 weeks;
                  }
                  // If presale end time is set but not start time, set default start time
                  if (_config.presaleEndTime > 0 && _config.presaleStartTime == 0) {
                      HeyMintStorage.state().cfg.presaleStartTime = uint32(
                          block.timestamp
                      );
                  }
              }
              // ============ BASE FUNCTIONALITY ============
              function pause() external onlyOwner {
                  _pause();
              }
              function unpause() external onlyOwner {
                  _unpause();
              }
              /**
               * @notice Returns true if the contract implements the interface defined by interfaceId
               * @param interfaceId The interface identifier, as specified in ERC-165
               */
              function supportsInterface(
                  bytes4 interfaceId
              )
                  public
                  view
                  virtual
                  override(HeyMintERC721AUpgradeable, IERC165Upgradeable)
                  returns (bool)
              {
                  return HeyMintERC721AUpgradeable.supportsInterface(interfaceId);
              }
              // ============ METADATA ============
              /**
               * @notice Returns the base URI for all tokens. If the base URI is not set, it will be generated based on the project ID
               */
              function _baseURI() internal view virtual override returns (string memory) {
                  return HeyMintStorage.state().cfg.uriBase;
              }
              /**
               * @notice Overrides the default ERC721 tokenURI function to look for specific token URIs when present
               * @param tokenId The token ID to query
               */
              function tokenURI(
                  uint256 tokenId
              )
                  public
                  view
                  virtual
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  returns (string memory)
              {
                  if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  string memory specificTokenURI = state.data.tokenURIs[tokenId];
                  if (bytes(specificTokenURI).length != 0) return specificTokenURI;
                  string memory baseURI = _baseURI();
                  if (bytes(baseURI).length == 0) return "";
                  uint256 burnTokenId = state.data.tokenIdToBurnTokenId[tokenId];
                  uint256 tokenURITokenId = state.advCfg.useBurnTokenIdForMetadata &&
                      burnTokenId != 0
                      ? burnTokenId
                      : tokenId;
                  return string(abi.encodePacked(baseURI, _toString(tokenURITokenId)));
              }
              /**
               * @notice Update the base token URI
               * @param _newBaseURI The new base URI to use
               */
              function setBaseURI(string calldata _newBaseURI) external onlyOwner {
                  require(!HeyMintStorage.state().advCfg.metadataFrozen, "NOT_ACTIVE");
                  HeyMintStorage.state().cfg.uriBase = _newBaseURI;
              }
              /**
               * @notice Freeze metadata so it can never be changed again
               */
              function freezeMetadata() external onlyOwner {
                  HeyMintStorage.state().advCfg.metadataFrozen = true;
              }
              // ============ ERC-2981 ROYALTY ============
              /**
               * @notice Basic gas saving implementation of ERC-2981 royaltyInfo function with receiver set to the contract owner
               * @param _salePrice The sale price used to determine the royalty amount
               */
              function royaltyInfo(
                  uint256,
                  uint256 _salePrice
              ) external view override returns (address, uint256) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  address payoutAddress = state.advCfg.royaltyPayoutAddress !=
                      address(0x0)
                      ? state.advCfg.royaltyPayoutAddress
                      : owner();
                  if (payoutAddress == address(0x0)) {
                      return (payoutAddress, 0);
                  }
                  return (payoutAddress, (_salePrice * state.cfg.royaltyBps) / 10000);
              }
              // ============ PAYOUT ============
              /**
               * @notice Withdraws all funds held within contract
               */
              function withdraw() external nonReentrant onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (state.cfg.fundingEndsAt > 0) {
                      require(
                          state.data.fundingTargetReached,
                          "FUNDING_TARGET_NOT_REACHED"
                      );
                  }
                  if (state.advCfg.refundEndsAt > 0) {
                      require(!refundGuaranteeActive(), "REFUND_GUARANTEE_STILL_ACTIVE");
                  }
                  uint256 balance = address(this).balance;
                  if (state.advCfg.payoutAddresses.length == 0) {
                      (bool success, ) = payable(owner()).call{value: balance}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      for (uint256 i = 0; i < state.advCfg.payoutAddresses.length; i++) {
                          uint256 amount = (balance * state.advCfg.payoutBasisPoints[i]) /
                              10000;
                          (bool success, ) = HeyMintStorage
                              .state()
                              .advCfg
                              .payoutAddresses[i]
                              .call{value: amount}("");
                          require(success, "TRANSFER_FAILED");
                      }
                  }
              }
              // ============ PUBLIC SALE ============
              /**
               * @notice Returns the public price in wei. Public price is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function publicPriceInWei() public view returns (uint256) {
                  return uint256(HeyMintStorage.state().cfg.publicPrice) * 10 ** 13;
              }
              /**
               * @notice To be updated by contract owner to allow public sale minting
               */
              function setPublicSaleState(bool _saleActiveState) external onlyOwner {
                  HeyMintStorage.state().cfg.publicSaleActive = _saleActiveState;
              }
              /**
               * @notice Update the public mint price
               * @param _publicPrice The new public mint price to use
               */
              function setPublicPrice(uint32 _publicPrice) external onlyOwner {
                  HeyMintStorage.state().cfg.publicPrice = _publicPrice;
              }
              /**
               * @notice Set the maximum mints allowed per a given address in the public sale
               * @param _mintsAllowed The new maximum mints allowed per address
               */
              function setPublicMintsAllowedPerAddress(
                  uint8 _mintsAllowed
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.publicMintsAllowedPerAddress = _mintsAllowed;
              }
              /**
               * @notice Set the maximum mints allowed per a given transaction in the public sale
               * @param _mintsAllowed The new maximum mints allowed per transaction
               */
              function setPublicMintsAllowedPerTransaction(
                  uint8 _mintsAllowed
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .cfg
                      .publicMintsAllowedPerTransaction = _mintsAllowed;
              }
              /**
               * @notice Update the start time for public mint
               * @param _publicSaleStartTime The new start time for public mint
               */
              function setPublicSaleStartTime(
                  uint32 _publicSaleStartTime
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.publicSaleStartTime = _publicSaleStartTime;
              }
              /**
               * @notice Update the end time for public mint
               * @param _publicSaleEndTime The new end time for public mint
               */
              function setPublicSaleEndTime(
                  uint32 _publicSaleEndTime
              ) external onlyOwner {
                  require(_publicSaleEndTime > block.timestamp, "TIME_IN_PAST");
                  HeyMintStorage.state().cfg.publicSaleEndTime = _publicSaleEndTime;
              }
              /**
               * @notice Update whether or not to use the automatic public sale times
               * @param _usePublicSaleTimes Whether or not to use the automatic public sale times
               */
              function setUsePublicSaleTimes(
                  bool _usePublicSaleTimes
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.usePublicSaleTimes = _usePublicSaleTimes;
              }
              /**
               * @notice Returns if public sale times are active. If required config settings are not set, returns true.
               */
              function publicSaleTimeIsActive() public view returns (bool) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (
                      state.cfg.usePublicSaleTimes == false ||
                      state.cfg.publicSaleStartTime == 0 ||
                      state.cfg.publicSaleEndTime == 0
                  ) {
                      return true;
                  }
                  return
                      block.timestamp >= state.cfg.publicSaleStartTime &&
                      block.timestamp <= state.cfg.publicSaleEndTime;
              }
              /**
               * @notice Allow for public minting of tokens
               * @param _numTokens The number of tokens to mint
               */
              function publicMint(uint256 _numTokens) external payable nonReentrant {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  require(cfg.publicSaleActive, "NOT_ACTIVE");
                  require(publicSaleTimeIsActive(), "NOT_ACTIVE");
                  require(
                      cfg.publicMintsAllowedPerAddress == 0 ||
                          _numberMinted(msg.sender) + _numTokens <=
                          cfg.publicMintsAllowedPerAddress,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      cfg.publicMintsAllowedPerTransaction == 0 ||
                          _numTokens <= cfg.publicMintsAllowedPerTransaction,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      totalSupply() + _numTokens <= cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  uint256 publicPrice = publicPriceInWei();
                  if (cfg.heyMintFeeActive) {
                      uint256 heymintFee = _numTokens * heymintFeePerToken();
                      require(
                          msg.value == publicPrice * _numTokens + heymintFee,
                          "INVALID_PRICE_PAID"
                      );
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      require(
                          msg.value == publicPrice * _numTokens,
                          "INVALID_PRICE_PAID"
                      );
                  }
                  if (cfg.fundingEndsAt > 0) {
                      uint256 firstTokenIdToMint = _nextTokenId();
                      for (uint256 i = 0; i < _numTokens; i++) {
                          HeyMintStorage.state().data.pricePaid[
                              firstTokenIdToMint + i
                          ] = publicPrice;
                      }
                  }
                  _safeMint(msg.sender, _numTokens);
              }
              // ============ REFUND ============
              /**
               * Will return true if token holders can still return their tokens for a refund
               */
              function refundGuaranteeActive() public view returns (bool) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  return block.timestamp < state.advCfg.refundEndsAt;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {HeyMintERC721AUpgradeable} from "./HeyMintERC721AUpgradeable.sol";
          import {BaseConfig, AdvancedConfig, BurnToken, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          contract HeyMintERC721AExtensionA is HeyMintERC721AUpgradeable {
              using HeyMintStorage for HeyMintStorage.State;
              event Stake(uint256 indexed tokenId);
              event Unstake(uint256 indexed tokenId);
              event Loan(address from, address to, uint256 tokenId);
              event LoanRetrieved(address from, address to, uint256 tokenId);
              // ============ BASE FUNCTIONALITY ============
              /**
               * @notice Returns all storage variables for the contract
               */
              function getSettings()
                  external
                  view
                  returns (
                      BaseConfig memory,
                      AdvancedConfig memory,
                      BurnToken[] memory,
                      bool,
                      bool,
                      bool,
                      uint256
                  )
              {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  return (
                      state.cfg,
                      state.advCfg,
                      state.burnTokens,
                      state.data.advancedConfigInitialized,
                      state.data.fundingTargetReached,
                      state.data.fundingSuccessDetermined,
                      state.data.currentLoanTotal
                  );
              }
              /**
               * @notice Updates the address configuration for the contract
               */
              function updateBaseConfig(
                  BaseConfig memory _baseConfig
              ) external onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(
                      _baseConfig.maxSupply <= state.cfg.maxSupply,
                      "CANNOT_UPDATE_CONSTANT_VARIABLE"
                  );
                  require(
                      _baseConfig.presaleMaxSupply <= state.cfg.presaleMaxSupply,
                      "CANNOT_UPDATE_CONSTANT_VARIABLE"
                  );
                  require(
                      _baseConfig.fundingEndsAt == state.cfg.fundingEndsAt,
                      "CANNOT_UPDATE_CONSTANT_VARIABLE"
                  );
                  require(
                      _baseConfig.fundingTarget == state.cfg.fundingTarget,
                      "CANNOT_UPDATE_CONSTANT_VARIABLE"
                  );
                  require(
                      _baseConfig.heyMintFeeActive == state.cfg.heyMintFeeActive,
                      "CANNOT_UPDATE_CONSTANT_VARIABLE"
                  );
                  if (state.advCfg.metadataFrozen) {
                      require(
                          keccak256(abi.encode(_baseConfig.uriBase)) ==
                              keccak256(abi.encode(state.cfg.uriBase)),
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                  }
                  state.cfg = _baseConfig;
              }
              /**
               * @notice Updates the advanced configuration for the contract
               */
              function updateAdvancedConfig(
                  AdvancedConfig memory _advancedConfig
              ) external onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (state.advCfg.metadataFrozen) {
                      require(
                          _advancedConfig.metadataFrozen,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                  }
                  if (state.advCfg.soulbindAdminTransfersPermanentlyDisabled) {
                      require(
                          _advancedConfig.soulbindAdminTransfersPermanentlyDisabled,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                  }
                  if (state.advCfg.refundEndsAt > 0) {
                      require(
                          _advancedConfig.refundPrice == state.advCfg.refundPrice,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                      require(
                          _advancedConfig.refundEndsAt >= state.advCfg.refundEndsAt,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                  } else if (
                      _advancedConfig.refundEndsAt > 0 || _advancedConfig.refundPrice > 0
                  ) {
                      require(
                          _advancedConfig.refundPrice > 0,
                          "REFUND_PRICE_MUST_BE_SET"
                      );
                      require(
                          _advancedConfig.refundEndsAt > 0,
                          "REFUND_DURATION_MUST_BE_SET"
                      );
                  }
                  if (!state.data.advancedConfigInitialized) {
                      state.data.advancedConfigInitialized = true;
                  }
                  uint256 payoutAddressesLength = _advancedConfig.payoutAddresses.length;
                  uint256 payoutBasisPointsLength = _advancedConfig
                      .payoutBasisPoints
                      .length;
                  if (state.advCfg.payoutAddressesFrozen) {
                      require(
                          _advancedConfig.payoutAddressesFrozen,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                      require(
                          payoutAddressesLength == state.advCfg.payoutAddresses.length,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                      require(
                          payoutBasisPointsLength ==
                              state.advCfg.payoutBasisPoints.length,
                          "CANNOT_UPDATE_CONSTANT_VARIABLE"
                      );
                      for (uint256 i = 0; i < payoutAddressesLength; i++) {
                          require(
                              _advancedConfig.payoutAddresses[i] ==
                                  state.advCfg.payoutAddresses[i],
                              "CANNOT_UPDATE_CONSTANT_VARIABLE"
                          );
                          require(
                              _advancedConfig.payoutBasisPoints[i] ==
                                  state.advCfg.payoutBasisPoints[i],
                              "CANNOT_UPDATE_CONSTANT_VARIABLE"
                          );
                      }
                  } else if (payoutAddressesLength > 0) {
                      require(
                          payoutAddressesLength == payoutBasisPointsLength,
                          "ARRAY_LENGTHS_MUST_MATCH"
                      );
                      uint256 totalBasisPoints = 0;
                      for (uint256 i = 0; i < payoutBasisPointsLength; i++) {
                          totalBasisPoints += _advancedConfig.payoutBasisPoints[i];
                      }
                      require(totalBasisPoints == 10000, "BASIS_POINTS_MUST_EQUAL_10000");
                  }
                  state.advCfg = _advancedConfig;
              }
              /**
               * @notice Reduce the max supply of tokens
               * @param _newMaxSupply The new maximum supply of tokens available to mint
               */
              function reduceMaxSupply(uint16 _newMaxSupply) external onlyOwner {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  require(_newMaxSupply < cfg.maxSupply, "NEW_MAX_SUPPLY_TOO_HIGH");
                  require(
                      _newMaxSupply >= totalSupply(),
                      "SUPPLY_LOWER_THAN_MINTED_TOKENS"
                  );
                  cfg.maxSupply = _newMaxSupply;
              }
              // ============ PAYOUT ============
              /**
               * @notice Freeze all payout addresses so they can never be changed again
               */
              function freezePayoutAddresses() external onlyOwner {
                  HeyMintStorage.state().advCfg.payoutAddressesFrozen = true;
              }
              /**
               * @notice Update payout addresses and basis points for each addresses' respective share of contract funds
               * @param _payoutAddresses The new payout addresses to use
               * @param _payoutBasisPoints The amount to pay out to each address in _payoutAddresses (in basis points)
               */
              function updatePayoutAddressesAndBasisPoints(
                  address[] calldata _payoutAddresses,
                  uint16[] calldata _payoutBasisPoints
              ) external onlyOwner {
                  AdvancedConfig storage advCfg = HeyMintStorage.state().advCfg;
                  uint256 payoutBasisPointsLength = _payoutBasisPoints.length;
                  require(
                      !advCfg.payoutAddressesFrozen,
                      "CANNOT_UPDATE_CONSTANT_VARIABLE"
                  );
                  require(
                      _payoutAddresses.length == payoutBasisPointsLength,
                      "ARRAY_LENGTHS_MUST_MATCH"
                  );
                  uint256 totalBasisPoints = 0;
                  for (uint256 i = 0; i < payoutBasisPointsLength; i++) {
                      totalBasisPoints += _payoutBasisPoints[i];
                  }
                  require(totalBasisPoints == 10000, "BASIS_POINTS_MUST_EQUAL_10000");
                  advCfg.payoutAddresses = _payoutAddresses;
                  advCfg.payoutBasisPoints = _payoutBasisPoints;
              }
              // ============ ERC-2981 ROYALTY ============
              /**
               * @notice Updates royalty basis points
               * @param _royaltyBps The new royalty basis points to use
               */
              function setRoyaltyBasisPoints(uint16 _royaltyBps) external onlyOwner {
                  HeyMintStorage.state().cfg.royaltyBps = _royaltyBps;
              }
              /**
               * @notice Updates royalty payout address
               * @param _royaltyPayoutAddress The new royalty payout address to use
               */
              function setRoyaltyPayoutAddress(
                  address _royaltyPayoutAddress
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .advCfg
                      .royaltyPayoutAddress = _royaltyPayoutAddress;
              }
              // ============ GIFT ============
              /**
               * @notice Allow owner to send 'mintNumber' tokens without cost to multiple addresses
               * @param _receivers The addresses to send the tokens to
               * @param _mintNumber The number of tokens to send to each address
               */
              function gift(
                  address[] calldata _receivers,
                  uint256[] calldata _mintNumber
              ) external payable onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(
                      _receivers.length == _mintNumber.length,
                      "ARRAY_LENGTHS_MUST_MATCH"
                  );
                  uint256 totalMints = 0;
                  for (uint256 i = 0; i < _mintNumber.length; i++) {
                      totalMints += _mintNumber[i];
                  }
                  require(
                      totalSupply() + totalMints <= state.cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  if (state.cfg.heyMintFeeActive) {
                      uint256 heymintFee = (totalMints * heymintFeePerToken()) / 10;
                      require(msg.value == heymintFee, "PAYMENT_INCORRECT");
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  }
                  for (uint256 i = 0; i < _receivers.length; i++) {
                      _safeMint(_receivers[i], _mintNumber[i]);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {HeyMintERC721AUpgradeable} from "./HeyMintERC721AUpgradeable.sol";
          import {HeyMintStorage, BaseConfig, BurnToken} from "../libraries/HeyMintStorage.sol";
          import {ECDSAUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
          import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
          import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
          contract HeyMintERC721AExtensionB is HeyMintERC721AUpgradeable {
              using HeyMintStorage for HeyMintStorage.State;
              using ECDSAUpgradeable for bytes32;
              // Address where burnt tokens are sent.
              address public constant burnAddress =
                  0x000000000000000000000000000000000000dEaD;
              // ============ PRESALE ============
              /**
               * @notice Returns the presale price in wei. Presale price is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function presalePriceInWei() public view returns (uint256) {
                  return uint256(HeyMintStorage.state().cfg.presalePrice) * 10 ** 13;
              }
              /**
               * @notice To be updated by contract owner to allow presale minting
               * @param _saleActiveState The new presale activ
               .e state
               */
              function setPresaleState(bool _saleActiveState) external onlyOwner {
                  HeyMintStorage.state().cfg.presaleActive = _saleActiveState;
              }
              /**
               * @notice Update the presale mint price
               * @param _presalePrice The new presale mint price to use
               */
              function setPresalePrice(uint32 _presalePrice) external onlyOwner {
                  HeyMintStorage.state().cfg.presalePrice = _presalePrice;
              }
              /**
               * @notice Reduce the max supply of tokens available to mint in the presale
               * @param _newPresaleMaxSupply The new maximum supply of presale tokens available to mint
               */
              function reducePresaleMaxSupply(
                  uint16 _newPresaleMaxSupply
              ) external onlyOwner {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  require(
                      _newPresaleMaxSupply < cfg.presaleMaxSupply,
                      "NEW_MAX_SUPPLY_TOO_HIGH"
                  );
                  cfg.presaleMaxSupply = _newPresaleMaxSupply;
              }
              /**
               * @notice Set the maximum mints allowed per a given address in the presale
               * @param _mintsAllowed The new maximum mints allowed per address in the presale
               */
              function setPresaleMintsAllowedPerAddress(
                  uint8 _mintsAllowed
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .cfg
                      .presaleMintsAllowedPerAddress = _mintsAllowed;
              }
              /**
               * @notice Set the maximum mints allowed per a given transaction in the presale
               * @param _mintsAllowed The new maximum mints allowed per transaction in the presale
               */
              function setPresaleMintsAllowedPerTransaction(
                  uint8 _mintsAllowed
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .cfg
                      .presaleMintsAllowedPerTransaction = _mintsAllowed;
              }
              /**
               * @notice Set the signer address used to verify presale minting
               * @param _presaleSignerAddress The new signer address to use
               */
              function setPresaleSignerAddress(
                  address _presaleSignerAddress
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.presaleSignerAddress = _presaleSignerAddress;
              }
              /**
               * @notice Update the start time for presale mint
               */
              function setPresaleStartTime(uint32 _presaleStartTime) external onlyOwner {
                  HeyMintStorage.state().cfg.presaleStartTime = _presaleStartTime;
              }
              /**
               * @notice Update the end time for presale mint
               */
              function setPresaleEndTime(uint32 _presaleEndTime) external onlyOwner {
                  require(_presaleEndTime > block.timestamp, "TIME_IN_PAST");
                  HeyMintStorage.state().cfg.presaleEndTime = _presaleEndTime;
              }
              /**
               * @notice Update whether or not to use the automatic presale times
               */
              function setUsePresaleTimes(bool _usePresaleTimes) external onlyOwner {
                  HeyMintStorage.state().cfg.usePresaleTimes = _usePresaleTimes;
              }
              /**
               * @notice Returns if presale times are active. If required config settings are not set, returns true.
               */
              function presaleTimeIsActive() public view returns (bool) {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  if (
                      cfg.usePresaleTimes == false ||
                      cfg.presaleStartTime == 0 ||
                      cfg.presaleEndTime == 0
                  ) {
                      return true;
                  }
                  return
                      block.timestamp >= cfg.presaleStartTime &&
                      block.timestamp <= cfg.presaleEndTime;
              }
              /**
               * @notice Verify that a signed message is validly signed by the presaleSignerAddress
               * @param _messageHash The hash of the message to verify
               * @param _signature The signature of the messageHash to verify
               */
              function verifySignerAddress(
                  bytes32 _messageHash,
                  bytes calldata _signature
              ) private view returns (bool) {
                  return
                      HeyMintStorage.state().cfg.presaleSignerAddress ==
                      _messageHash.toEthSignedMessageHash().recover(_signature);
              }
              /**
               * @notice Allow for allowlist minting of tokens
               * @param _messageHash The hash of the message containing msg.sender & _maximumAllowedMints to verify
               * @param _signature The signature of the messageHash to verify
               * @param _numTokens The number of tokens to mint
               * @param _maximumAllowedMints The maximum number of tokens that can be minted by the caller
               */
              function presaleMint(
                  bytes32 _messageHash,
                  bytes calldata _signature,
                  uint256 _numTokens,
                  uint256 _maximumAllowedMints
              ) external payable nonReentrant {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  require(cfg.presaleActive, "NOT_ACTIVE");
                  require(presaleTimeIsActive(), "NOT_ACTIVE");
                  require(
                      cfg.presaleMintsAllowedPerAddress == 0 ||
                          _numberMinted(msg.sender) + _numTokens <=
                          cfg.presaleMintsAllowedPerAddress,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      cfg.presaleMintsAllowedPerTransaction == 0 ||
                          _numTokens <= cfg.presaleMintsAllowedPerTransaction,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      _numberMinted(msg.sender) + _numTokens <= _maximumAllowedMints,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      cfg.presaleMaxSupply == 0 ||
                          totalSupply() + _numTokens <= cfg.presaleMaxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  require(
                      totalSupply() + _numTokens <= cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  uint256 presalePrice = presalePriceInWei();
                  if (cfg.heyMintFeeActive) {
                      uint256 heymintFee = _numTokens * heymintFeePerToken();
                      require(
                          msg.value == presalePrice * _numTokens + heymintFee,
                          "INVALID_PRICE_PAID"
                      );
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      require(
                          msg.value == presalePrice * _numTokens,
                          "INVALID_PRICE_PAID"
                      );
                  }
                  require(
                      keccak256(abi.encode(msg.sender, _maximumAllowedMints)) ==
                          _messageHash,
                      "MESSAGE_INVALID"
                  );
                  require(
                      verifySignerAddress(_messageHash, _signature),
                      "INVALID_SIGNATURE"
                  );
                  if (cfg.fundingEndsAt > 0) {
                      uint256 firstTokenIdToMint = _nextTokenId();
                      for (uint256 i = 0; i < _numTokens; i++) {
                          HeyMintStorage.state().data.pricePaid[
                              firstTokenIdToMint + i
                          ] = presalePrice;
                      }
                  }
                  _safeMint(msg.sender, _numTokens);
              }
              // ============ BURN TO MINT ============
              /**
               * @notice Returns the burn payment in wei. Price is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function burnPaymentInWei() public view returns (uint256) {
                  return uint256(HeyMintStorage.state().advCfg.burnPayment) * 10 ** 13;
              }
              /**
               * @notice To be updated by contract owner to allow burning to claim a token
               * @param _burnClaimActive If true tokens can be burned in order to mint
               */
              function setBurnClaimState(bool _burnClaimActive) external onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (_burnClaimActive) {
                      require(state.burnTokens.length != 0, "NOT_CONFIGURED");
                      require(state.advCfg.mintsPerBurn != 0, "NOT_CONFIGURED");
                  }
                  state.advCfg.burnClaimActive = _burnClaimActive;
              }
              /**
               * @notice Set the contract address of the NFT to be burned in order to mint
               * @param _burnTokens An array of all tokens required for burning
               */
              function updateBurnTokens(
                  BurnToken[] calldata _burnTokens
              ) external onlyOwner {
                  BurnToken[] storage burnTokens = HeyMintStorage.state().burnTokens;
                  uint256 oldBurnTokensLength = burnTokens.length;
                  uint256 newBurnTokensLength = _burnTokens.length;
                  // Update the existing BurnTokens and push any new BurnTokens
                  for (uint256 i = 0; i < newBurnTokensLength; i++) {
                      if (i < oldBurnTokensLength) {
                          burnTokens[i] = _burnTokens[i];
                      } else {
                          burnTokens.push(_burnTokens[i]);
                      }
                  }
                  // Pop any extra BurnTokens if the new array is shorter
                  for (uint256 i = oldBurnTokensLength; i > newBurnTokensLength; i--) {
                      burnTokens.pop();
                  }
              }
              /**
               * @notice Update the number of free mints claimable per token burned
               * @param _mintsPerBurn The new number of tokens that can be minted per burn transaction
               */
              function updateMintsPerBurn(uint8 _mintsPerBurn) external onlyOwner {
                  HeyMintStorage.state().advCfg.mintsPerBurn = _mintsPerBurn;
              }
              /**
               * @notice Update the price required to be paid alongside a burn tx to mint (payment is per tx, not per token in the case of >1 mintsPerBurn)
               * @param _burnPayment The new amount of payment required per burn transaction
               */
              function updatePaymentPerBurn(uint32 _burnPayment) external onlyOwner {
                  HeyMintStorage.state().advCfg.burnPayment = _burnPayment;
              }
              /**
               * @notice If true, real token ids are used for metadata. If false, burn token ids are used for metadata if they exist.
               * @param _useBurnTokenIdForMetadata If true, burn token ids are used for metadata if they exist. If false, real token ids are used.
               */
              function setUseBurnTokenIdForMetadata(
                  bool _useBurnTokenIdForMetadata
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .advCfg
                      .useBurnTokenIdForMetadata = _useBurnTokenIdForMetadata;
              }
              /**
               * @notice Burn tokens from other contracts in order to mint tokens on this contract
               * @dev This contract must be approved by the caller to transfer the tokens being burned
               * @param _contracts The contracts of the tokens to burn in the same order as the array burnTokens
               * @param _tokenIds Nested array of token ids to burn for 721 and amounts to burn for 1155 corresponding to _contracts
               * @param _tokensToMint The number of tokens to mint
               */
              function burnToMint(
                  address[] calldata _contracts,
                  uint256[][] calldata _tokenIds,
                  uint256 _tokensToMint
              ) external payable nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  uint256 contractsLength = _contracts.length;
                  uint256 burnTokenLength = state.burnTokens.length;
                  require(burnTokenLength > 0, "NOT_CONFIGURED");
                  require(state.advCfg.mintsPerBurn != 0, "NOT_CONFIGURED");
                  require(state.advCfg.burnClaimActive, "NOT_ACTIVE");
                  require(
                      contractsLength == _tokenIds.length,
                      "ARRAY_LENGTHS_MUST_MATCH"
                  );
                  require(contractsLength == burnTokenLength, "ARRAY_LENGTHS_MUST_MATCH");
                  require(
                      totalSupply() + _tokensToMint <= state.cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  uint256 burnPayment = burnPaymentInWei();
                  uint256 burnPaymentTotal = burnPayment *
                      (_tokensToMint / state.advCfg.mintsPerBurn);
                  if (state.cfg.heyMintFeeActive) {
                      uint256 heymintFee = _tokensToMint * heymintFeePerToken();
                      require(
                          msg.value == burnPaymentTotal + heymintFee,
                          "INVALID_PRICE_PAID"
                      );
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      require(msg.value == burnPaymentTotal, "INVALID_PRICE_PAID");
                  }
                  for (uint256 i = 0; i < burnTokenLength; i++) {
                      BurnToken memory burnToken = state.burnTokens[i];
                      require(
                          burnToken.contractAddress == _contracts[i],
                          "INCORRECT_CONTRACT"
                      );
                      if (burnToken.tokenType == 1) {
                          uint256 _tokenIdsLength = _tokenIds[i].length;
                          require(
                              (_tokenIdsLength / burnToken.tokensPerBurn) *
                                  state.advCfg.mintsPerBurn ==
                                  _tokensToMint,
                              "INCORRECT_NO_OF_TOKENS_TO_BURN"
                          );
                          for (uint256 j = 0; j < _tokenIdsLength; j++) {
                              IERC721 burnContract = IERC721(_contracts[i]);
                              uint256 tokenId = _tokenIds[i][j];
                              require(
                                  burnContract.ownerOf(tokenId) == msg.sender,
                                  "MUST_OWN_TOKEN"
                              );
                              burnContract.transferFrom(msg.sender, burnAddress, tokenId);
                          }
                      } else if (burnToken.tokenType == 2) {
                          uint256 amountToBurn = _tokenIds[i][0];
                          require(
                              (amountToBurn / burnToken.tokensPerBurn) *
                                  state.advCfg.mintsPerBurn ==
                                  _tokensToMint,
                              "INCORRECT_NO_OF_TOKENS_TO_BURN"
                          );
                          IERC1155 burnContract = IERC1155(_contracts[i]);
                          require(
                              burnContract.balanceOf(msg.sender, burnToken.tokenId) >=
                                  amountToBurn,
                              "MUST_OWN_TOKEN"
                          );
                          burnContract.safeTransferFrom(
                              msg.sender,
                              burnAddress,
                              burnToken.tokenId,
                              amountToBurn,
                              ""
                          );
                      }
                  }
                  if (state.advCfg.useBurnTokenIdForMetadata) {
                      require(
                          _tokenIds[0].length == _tokensToMint,
                          "BURN_TOKENS_MUST_MATCH_MINT_NO"
                      );
                      uint256 firstNewTokenId = _nextTokenId();
                      for (uint256 i = 0; i < _tokensToMint; i++) {
                          state.data.tokenIdToBurnTokenId[
                              firstNewTokenId + i
                          ] = _tokenIds[0][i];
                      }
                  }
                  _safeMint(msg.sender, _tokensToMint);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {HeyMintERC721AUpgradeable} from "./HeyMintERC721AUpgradeable.sol";
          import {AdvancedConfig, Data, BaseConfig, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
          contract HeyMintERC721AExtensionC is HeyMintERC721AUpgradeable {
              using HeyMintStorage for HeyMintStorage.State;
              event Stake(uint256 indexed tokenId);
              event Unstake(uint256 indexed tokenId);
              // ============ BASE FUNCTIONALITY ============
              /**
               * @notice Update the specific token URI for a set of tokens
               * @param _tokenIds The token IDs to update
               * @param _newURIs The new URIs to use
               */
              function setTokenURIs(
                  uint256[] calldata _tokenIds,
                  string[] calldata _newURIs
              ) external onlyOwner {
                  require(!HeyMintStorage.state().advCfg.metadataFrozen, "NOT_ACTIVE");
                  uint256 tokenIdsLength = _tokenIds.length;
                  require(tokenIdsLength == _newURIs.length);
                  for (uint256 i = 0; i < tokenIdsLength; i++) {
                      HeyMintStorage.state().data.tokenURIs[_tokenIds[i]] = _newURIs[i];
                  }
              }
              function baseTokenURI() external view returns (string memory) {
                  return HeyMintStorage.state().cfg.uriBase;
              }
              // ============ CREDIT CARD PAYMENT ============
              /**
               * @notice Returns the public price in wei. Public price is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function _publicPriceInWei() internal view returns (uint256) {
                  return uint256(HeyMintStorage.state().cfg.publicPrice) * 10 ** 13;
              }
              /**
               * @notice Returns if public sale times are active. If required config settings are not set, returns true.
               */
              function _publicSaleTimeIsActive() internal view returns (bool) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (
                      state.cfg.usePublicSaleTimes == false ||
                      state.cfg.publicSaleStartTime == 0 ||
                      state.cfg.publicSaleEndTime == 0
                  ) {
                      return true;
                  }
                  return
                      block.timestamp >= state.cfg.publicSaleStartTime &&
                      block.timestamp <= state.cfg.publicSaleEndTime;
              }
              /**
               * @notice Returns an array of default addresses authorized to call creditCardMint
               */
              function getDefaultCreditCardMintAddresses()
                  public
                  pure
                  returns (address[5] memory)
              {
                  return [
                      0xf3DB642663231887E2Ff3501da6E3247D8634A6D,
                      0x5e01a33C75931aD0A91A12Ee016Be8D61b24ADEB,
                      0x9E733848061e4966c4a920d5b99a123459670aEe,
                      0x7754B94345BCE520f8dd4F6a5642567603e90E10,
                      0xdAb1a1854214684acE522439684a145E62505233
                  ];
              }
              /**
               * @notice Set an address authorized to call creditCardMint
               * @param _creditCardMintAddress The new address to authorize
               */
              function setCreditCardMintAddress(
                  address _creditCardMintAddress
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .advCfg
                      .creditCardMintAddress = _creditCardMintAddress;
              }
              function creditCardMint(
                  uint256 _numTokens,
                  address _to
              ) external payable nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  address[5]
                      memory defaultAddresses = getDefaultCreditCardMintAddresses();
                  bool authorized = false;
                  for (uint256 i = 0; i < defaultAddresses.length; i++) {
                      if (msg.sender == defaultAddresses[i]) {
                          authorized = true;
                          break;
                      }
                  }
                  require(
                      authorized || msg.sender == state.advCfg.creditCardMintAddress,
                      "NOT_AUTHORIZED_ADDRESS"
                  );
                  require(state.cfg.publicSaleActive, "NOT_ACTIVE");
                  require(_publicSaleTimeIsActive(), "NOT_ACTIVE");
                  require(
                      state.cfg.publicMintsAllowedPerAddress == 0 ||
                          _numberMinted(_to) + _numTokens <=
                          state.cfg.publicMintsAllowedPerAddress,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      state.cfg.publicMintsAllowedPerTransaction == 0 ||
                          _numTokens <= state.cfg.publicMintsAllowedPerTransaction,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      totalSupply() + _numTokens <= state.cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  uint256 publicPrice = _publicPriceInWei();
                  if (state.cfg.heyMintFeeActive) {
                      uint256 heymintFee = _numTokens * heymintFeePerToken();
                      require(
                          msg.value == publicPrice * _numTokens + heymintFee,
                          "INVALID_PRICE_PAID"
                      );
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      require(
                          msg.value == publicPrice * _numTokens,
                          "INVALID_PRICE_PAID"
                      );
                  }
                  if (state.cfg.fundingEndsAt > 0) {
                      uint256 firstTokenIdToMint = _nextTokenId();
                      for (uint256 i = 0; i < _numTokens; i++) {
                          HeyMintStorage.state().data.pricePaid[
                              firstTokenIdToMint + i
                          ] = publicPrice;
                      }
                  }
                  _safeMint(_to, _numTokens);
                  if (totalSupply() >= state.cfg.maxSupply) {
                      state.cfg.publicSaleActive = false;
                  }
              }
              // ============ SOULBINDING ============
              /**
               * @notice Change the admin address used to transfer tokens if needed.
               * @param _adminAddress The new soulbound admin address
               */
              function setSoulboundAdminAddress(
                  address _adminAddress
              ) external onlyOwner {
                  AdvancedConfig storage advCfg = HeyMintStorage.state().advCfg;
                  require(!advCfg.soulbindAdminTransfersPermanentlyDisabled);
                  advCfg.soulboundAdminAddress = _adminAddress;
              }
              /**
               * @notice Disallow admin transfers of soulbound tokens permanently.
               */
              function disableSoulbindAdminTransfersPermanently() external onlyOwner {
                  AdvancedConfig storage advCfg = HeyMintStorage.state().advCfg;
                  advCfg.soulboundAdminAddress = address(0);
                  advCfg.soulbindAdminTransfersPermanentlyDisabled = true;
              }
              /**
               * @notice Turn soulbinding on or off
               * @param _soulbindingActive If true soulbinding is active
               */
              function setSoulbindingState(bool _soulbindingActive) external onlyOwner {
                  HeyMintStorage.state().cfg.soulbindingActive = _soulbindingActive;
              }
              /**
               * @notice Allows an admin address to initiate token transfers if user wallets get hacked or lost
               * This function can only be used on soulbound tokens to prevent arbitrary transfers of normal tokens
               * @param _from The address to transfer from
               * @param _to The address to transfer to
               * @param _tokenId The token id to transfer
               */
              function soulboundAdminTransfer(
                  address _from,
                  address _to,
                  uint256 _tokenId
              ) external {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  address adminAddress = state.advCfg.soulboundAdminAddress == address(0)
                      ? owner()
                      : state.advCfg.soulboundAdminAddress;
                  require(msg.sender == adminAddress, "NOT_ADMIN");
                  require(state.cfg.soulbindingActive, "NOT_ACTIVE");
                  require(
                      !state.advCfg.soulbindAdminTransfersPermanentlyDisabled,
                      "NOT_ACTIVE"
                  );
                  state.data.soulboundAdminTransferInProgress = true;
                  _directApproveMsgSenderFor(_tokenId);
                  safeTransferFrom(_from, _to, _tokenId);
                  state.data.soulboundAdminTransferInProgress = false;
              }
              // ============ STAKING ============
              /**
               * @notice Turn staking on or off
               * @param _stakingState The new state of staking (true = on, false = off)
               */
              function setStakingState(bool _stakingState) external onlyOwner {
                  HeyMintStorage.state().advCfg.stakingActive = _stakingState;
              }
              /**
               * @notice Stake an arbitrary number of tokens
               * @param _tokenIds The ids of the tokens to stake
               */
              function stakeTokens(uint256[] calldata _tokenIds) external {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(state.advCfg.stakingActive, "NOT_ACTIVE");
                  for (uint256 i = 0; i < _tokenIds.length; i++) {
                      uint256 tokenId = _tokenIds[i];
                      require(ownerOf(tokenId) == msg.sender, "MUST_OWN_TOKEN");
                      if (state.data.currentTimeStaked[tokenId] == 0) {
                          state.data.currentTimeStaked[tokenId] = block.timestamp;
                          emit Stake(tokenId);
                      }
                  }
              }
              /**
               * @notice Unstake an arbitrary number of tokens
               * @param _tokenIds The ids of the tokens to unstake
               */
              function unstakeTokens(uint256[] calldata _tokenIds) external {
                  Data storage data = HeyMintStorage.state().data;
                  for (uint256 i = 0; i < _tokenIds.length; i++) {
                      uint256 tokenId = _tokenIds[i];
                      require(ownerOf(tokenId) == msg.sender, "MUST_OWN_TOKEN");
                      if (data.currentTimeStaked[tokenId] != 0) {
                          data.totalTimeStaked[tokenId] +=
                              block.timestamp -
                              data.currentTimeStaked[tokenId];
                          data.currentTimeStaked[tokenId] = 0;
                          emit Unstake(tokenId);
                      }
                  }
              }
              /**
               * @notice Allows for transfers (not sales) while staking
               * @param _from The address of the current owner of the token
               * @param _to The address of the new owner of the token
               * @param _tokenId The id of the token to transfer
               */
              function stakingTransfer(
                  address _from,
                  address _to,
                  uint256 _tokenId
              ) external {
                  Data storage data = HeyMintStorage.state().data;
                  require(ownerOf(_tokenId) == msg.sender, "MUST_OWN_TOKEN");
                  data.stakingTransferActive = true;
                  safeTransferFrom(_from, _to, _tokenId);
                  data.stakingTransferActive = false;
              }
              /**
               * @notice Allow contract owner to forcibly unstake a token if needed
               * @param _tokenId The id of the token to unstake
               */
              function adminUnstake(uint256 _tokenId) external onlyOwner {
                  Data storage data = HeyMintStorage.state().data;
                  require(HeyMintStorage.state().data.currentTimeStaked[_tokenId] != 0);
                  data.totalTimeStaked[_tokenId] +=
                      block.timestamp -
                      data.currentTimeStaked[_tokenId];
                  data.currentTimeStaked[_tokenId] = 0;
                  emit Unstake(_tokenId);
              }
              /**
               * @notice Return the total amount of time a token has been staked
               * @param _tokenId The id of the token to check
               */
              function totalTokenStakeTime(
                  uint256 _tokenId
              ) external view returns (uint256) {
                  Data storage data = HeyMintStorage.state().data;
                  uint256 currentStakeStartTime = data.currentTimeStaked[_tokenId];
                  if (currentStakeStartTime != 0) {
                      return
                          (block.timestamp - currentStakeStartTime) +
                          data.totalTimeStaked[_tokenId];
                  }
                  return data.totalTimeStaked[_tokenId];
              }
              /**
               * @notice Return the amount of time a token has been currently staked
               * @param _tokenId The id of the token to check
               */
              function currentTokenStakeTime(
                  uint256 _tokenId
              ) external view returns (uint256) {
                  uint256 currentStakeStartTime = HeyMintStorage
                      .state()
                      .data
                      .currentTimeStaked[_tokenId];
                  if (currentStakeStartTime != 0) {
                      return block.timestamp - currentStakeStartTime;
                  }
                  return 0;
              }
              // ============ FREE CLAIM ============
              /**
               * @notice To be updated by contract owner to allow free claiming tokens
               * @param _freeClaimActive If true tokens can be claimed for free
               */
              function setFreeClaimState(bool _freeClaimActive) external onlyOwner {
                  AdvancedConfig storage advCfg = HeyMintStorage.state().advCfg;
                  if (_freeClaimActive) {
                      require(
                          advCfg.freeClaimContractAddress != address(0),
                          "NOT_CONFIGURED"
                      );
                      require(advCfg.mintsPerFreeClaim != 0, "NOT_CONFIGURED");
                  }
                  advCfg.freeClaimActive = _freeClaimActive;
              }
              /**
               * @notice Set the contract address of the NFT eligible for free claim
               * @param _freeClaimContractAddress The new contract address
               */
              function setFreeClaimContractAddress(
                  address _freeClaimContractAddress
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .advCfg
                      .freeClaimContractAddress = _freeClaimContractAddress;
              }
              /**
               * @notice Update the number of free mints claimable per token redeemed from the external ERC721 contract
               * @param _mintsPerFreeClaim The new number of free mints per token redeemed
               */
              function updateMintsPerFreeClaim(
                  uint8 _mintsPerFreeClaim
              ) external onlyOwner {
                  HeyMintStorage.state().advCfg.mintsPerFreeClaim = _mintsPerFreeClaim;
              }
              /**
               * @notice Check if an array of tokens is eligible for free claim
               * @param _tokenIDs The ids of the tokens to check
               */
              function checkFreeClaimEligibility(
                  uint256[] calldata _tokenIDs
              ) external view returns (bool[] memory) {
                  Data storage data = HeyMintStorage.state().data;
                  bool[] memory eligible = new bool[](_tokenIDs.length);
                  for (uint256 i = 0; i < _tokenIDs.length; i++) {
                      eligible[i] = !data.freeClaimUsed[_tokenIDs[i]];
                  }
                  return eligible;
              }
              /**
               * @notice Free claim token when msg.sender owns the token in the external contract
               * @param _tokenIDs The ids of the tokens to redeem
               */
              function freeClaim(
                  uint256[] calldata _tokenIDs
              ) external payable nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  uint256 tokenIdsLength = _tokenIDs.length;
                  uint256 totalMints = tokenIdsLength * state.advCfg.mintsPerFreeClaim;
                  require(
                      state.advCfg.freeClaimContractAddress != address(0),
                      "NOT_CONFIGURED"
                  );
                  require(state.advCfg.mintsPerFreeClaim != 0, "NOT_CONFIGURED");
                  require(state.advCfg.freeClaimActive, "NOT_ACTIVE");
                  require(
                      totalSupply() + totalMints <= state.cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  if (state.cfg.heyMintFeeActive) {
                      uint256 heymintFee = totalMints * heymintFeePerToken();
                      require(msg.value == heymintFee, "PAYMENT_INCORRECT");
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  }
                  IERC721 ExternalERC721FreeClaimContract = IERC721(
                      state.advCfg.freeClaimContractAddress
                  );
                  for (uint256 i = 0; i < tokenIdsLength; i++) {
                      require(
                          ExternalERC721FreeClaimContract.ownerOf(_tokenIDs[i]) ==
                              msg.sender,
                          "MUST_OWN_TOKEN"
                      );
                      require(
                          !state.data.freeClaimUsed[_tokenIDs[i]],
                          "TOKEN_ALREADY_CLAIMED"
                      );
                      state.data.freeClaimUsed[_tokenIDs[i]] = true;
                  }
                  _safeMint(msg.sender, totalMints);
              }
              // ============ RANDOM HASH ============
              /**
               * @notice To be updated by contract owner to allow random hash generation
               * @param _randomHashActive true to enable random hash generation, false to disable
               */
              function setGenerateRandomHashState(
                  bool _randomHashActive
              ) external onlyOwner {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  cfg.randomHashActive = _randomHashActive;
              }
              /**
               * @notice Retrieve random hashes for an array of token ids
               * @param _tokenIDs The ids of the tokens to retrieve random hashes for
               */
              function getRandomHashes(
                  uint256[] calldata _tokenIDs
              ) external view returns (bytes32[] memory) {
                  Data storage data = HeyMintStorage.state().data;
                  bytes32[] memory randomHashes = new bytes32[](_tokenIDs.length);
                  for (uint256 i = 0; i < _tokenIDs.length; i++) {
                      randomHashes[i] = data.randomHashStore[_tokenIDs[i]];
                  }
                  return randomHashes;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {HeyMintERC721AUpgradeable} from "./HeyMintERC721AUpgradeable.sol";
          import {AdvancedConfig, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          import {MerkleProofUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
          import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
          contract HeyMintERC721AExtensionD is HeyMintERC721AUpgradeable {
              using HeyMintStorage for HeyMintStorage.State;
              event Loan(address from, address to, uint256 tokenId);
              event LoanRetrieved(address from, address to, uint256 tokenId);
              // Address of the HeyMint admin address
              address public constant heymintAdminAddress =
                  0x52EA5F96f004d174470901Ba3F1984D349f0D3eF;
              // Address where burnt tokens are sent.
              address public constant burnAddress =
                  0x000000000000000000000000000000000000dEaD;
              // ============ HEYMINT FEE ============
              /**
               * @notice Allows the heymintAdminAddress to set the heymint fee per token
               * @param _heymintFeePerToken The new fee per token in wei
               */
              function setHeymintFeePerToken(uint256 _heymintFeePerToken) external {
                  require(msg.sender == heymintAdminAddress, "MUST_BE_HEYMINT_ADMIN");
                  HeyMintStorage.state().data.heymintFeePerToken = _heymintFeePerToken;
              }
              // ============ HEYMINT DEPOSIT TOKEN REDEMPTION ============
              /**
               * @notice Returns the deposit payment in wei. Deposit payment is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function remainingDepositPaymentInWei() public view returns (uint256) {
                  return
                      uint256(HeyMintStorage.state().advCfg.remainingDepositPayment) *
                      10 ** 13;
              }
              /**
               * @notice To be updated by contract owner to allow burning a deposit token to mint
               * @param _depositClaimActive If true deposit tokens can be burned in order to mint
               */
              function setDepositClaimState(bool _depositClaimActive) external onlyOwner {
                  AdvancedConfig storage advCfg = HeyMintStorage.state().advCfg;
                  if (_depositClaimActive) {
                      require(advCfg.depositMerkleRoot != bytes32(0), "NOT_CONFIGURED");
                      require(
                          advCfg.depositContractAddress != address(0),
                          "NOT_CONFIGURED"
                      );
                  }
                  advCfg.depositClaimActive = _depositClaimActive;
              }
              /**
               * @notice Set the merkle root used to validate the deposit tokens eligible for burning
               * @dev Each leaf in the merkle tree is the token id of a deposit token
               * @param _depositMerkleRoot The new merkle root
               */
              function setDepositMerkleRoot(
                  bytes32 _depositMerkleRoot
              ) external onlyOwner {
                  HeyMintStorage.state().advCfg.depositMerkleRoot = _depositMerkleRoot;
              }
              /**
               * @notice Set the address of the HeyMint deposit contract eligible for burning to mint
               * @param _depositContractAddress The new deposit contract address
               */
              function setDepositContractAddress(
                  address _depositContractAddress
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .advCfg
                      .depositContractAddress = _depositContractAddress;
              }
              /**
               * @notice Set the remaining payment required in order to mint along with burning a deposit token
               * @param _remainingDepositPayment The new remaining payment in centiETH
               */
              function setRemainingDepositPayment(
                  uint32 _remainingDepositPayment
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .advCfg
                      .remainingDepositPayment = _remainingDepositPayment;
              }
              /**
               * @notice Allows for burning deposit tokens in order to mint. The tokens must be eligible for burning.
               * Additional payment may be required in addition to burning the deposit tokens.
               * @dev This contract must be approved by the caller to transfer the deposit tokens being burned
               * @param _tokenIds The token ids of the deposit tokens to burn
               * @param _merkleProofs The merkle proofs for each token id verifying eligibility
               */
              function burnDepositTokensToMint(
                  uint256[] calldata _tokenIds,
                  bytes32[][] calldata _merkleProofs
              ) external payable nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(state.advCfg.depositMerkleRoot != bytes32(0), "NOT_CONFIGURED");
                  require(
                      state.advCfg.depositContractAddress != address(0),
                      "NOT_CONFIGURED"
                  );
                  require(state.advCfg.depositClaimActive, "NOT_ACTIVE");
                  uint256 numberOfTokens = _tokenIds.length;
                  require(numberOfTokens > 0, "NO_TOKEN_IDS_PROVIDED");
                  require(
                      numberOfTokens == _merkleProofs.length,
                      "ARRAY_LENGTHS_MUST_MATCH"
                  );
                  require(
                      totalSupply() + numberOfTokens <= state.cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  require(
                      msg.value == remainingDepositPaymentInWei() * numberOfTokens,
                      "INCORRECT_REMAINING_PAYMENT"
                  );
                  IERC721 DepositContract = IERC721(state.advCfg.depositContractAddress);
                  for (uint256 i = 0; i < numberOfTokens; i++) {
                      require(
                          MerkleProofUpgradeable.verify(
                              _merkleProofs[i],
                              state.advCfg.depositMerkleRoot,
                              keccak256(abi.encodePacked(_tokenIds[i]))
                          ),
                          "INVALID_MERKLE_PROOF"
                      );
                      require(
                          DepositContract.ownerOf(_tokenIds[i]) == msg.sender,
                          "MUST_OWN_TOKEN"
                      );
                      DepositContract.transferFrom(msg.sender, burnAddress, _tokenIds[i]);
                  }
                  _safeMint(msg.sender, numberOfTokens);
              }
              // ============ CONDITIONAL FUNDING ============
              /**
               * @notice Returns the funding target in wei. Funding target is stored with 2 decimals (1 = 0.01 ETH), so total 2 + 16 == 18 decimals
               */
              function fundingTargetInWei() public view returns (uint256) {
                  return uint256(HeyMintStorage.state().cfg.fundingTarget) * 10 ** 16;
              }
              /**
               * @notice To be called by anyone once the funding duration has passed to determine if the funding target was reached
               * If the funding target was not reached, all funds are refundable. Must be called before owner can withdraw funds
               */
              function determineFundingSuccess() external {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(state.cfg.fundingEndsAt > 0, "NOT_CONFIGURED");
                  require(
                      address(this).balance >= fundingTargetInWei(),
                      "FUNDING_TARGET_NOT_MET"
                  );
                  require(
                      !state.data.fundingSuccessDetermined,
                      "SUCCESS_ALREADY_DETERMINED"
                  );
                  state.data.fundingTargetReached = true;
                  state.data.fundingSuccessDetermined = true;
              }
              /**
               * @notice Burn tokens and return the price paid to the token owner if the funding target was not reached
               * Can be called starting 1 day after funding duration ends
               * @param _tokenIds The ids of the tokens to be refunded
               */
              function burnToRefund(uint256[] calldata _tokenIds) external nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  // Prevent refunding tokens on a contract where conditional funding has not been enabled
                  require(state.cfg.fundingEndsAt > 0, "NOT_CONFIGURED");
                  require(
                      block.timestamp > uint256(state.cfg.fundingEndsAt) + 1 days,
                      "FUNDING_PERIOD_STILL_ACTIVE"
                  );
                  require(!state.data.fundingTargetReached, "FUNDING_TARGET_WAS_MET");
                  require(
                      address(this).balance < fundingTargetInWei(),
                      "FUNDING_TARGET_WAS_MET"
                  );
                  uint256 totalRefund = 0;
                  for (uint256 i = 0; i < _tokenIds.length; i++) {
                      require(ownerOf(_tokenIds[i]) == msg.sender, "MUST_OWN_TOKEN");
                      require(
                          state.data.pricePaid[_tokenIds[i]] > 0,
                          "TOKEN_WAS_NOT_PURCHASED"
                      );
                      safeTransferFrom(
                          msg.sender,
                          0x000000000000000000000000000000000000dEaD,
                          _tokenIds[i]
                      );
                      totalRefund += state.data.pricePaid[_tokenIds[i]];
                  }
                  (bool success, ) = payable(msg.sender).call{value: totalRefund}("");
                  require(success, "TRANSFER_FAILED");
              }
              // ============ LOANING ============
              /**
               * @notice To be updated by contract owner to allow for loan functionality to turned on and off
               * @param _loaningActive The new state of loaning (true = on, false = off)
               */
              function setLoaningActive(bool _loaningActive) external onlyOwner {
                  HeyMintStorage.state().advCfg.loaningActive = _loaningActive;
              }
              /**
               * @notice Allow owner to loan their tokens to other addresses
               * @param _tokenId The id of the token to loan
               * @param _receiver The address of the receiver of the loan
               */
              function loan(uint256 _tokenId, address _receiver) external nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(
                      state.data.tokenOwnersOnLoan[_tokenId] == address(0),
                      "CANNOT_LOAN_BORROWED_TOKEN"
                  );
                  require(state.advCfg.loaningActive, "NOT_ACTIVE");
                  require(ownerOf(_tokenId) == msg.sender, "MUST_OWN_TOKEN");
                  require(_receiver != msg.sender, "CANNOT_LOAN_TO_SELF");
                  // Transfer the token - must do this before updating the mapping otherwise transfer will fail; nonReentrant modifier will prevent reentrancy
                  safeTransferFrom(msg.sender, _receiver, _tokenId);
                  // Add it to the mapping of originally loaned tokens
                  state.data.tokenOwnersOnLoan[_tokenId] = msg.sender;
                  // Add to the owner's loan balance
                  state.data.totalLoanedPerAddress[msg.sender] += 1;
                  state.data.currentLoanTotal += 1;
                  emit Loan(msg.sender, _receiver, _tokenId);
              }
              /**
               * @notice Allow the original owner of a token to retrieve a loaned token
               * @param _tokenId The id of the token to retrieve
               */
              function retrieveLoan(uint256 _tokenId) external nonReentrant {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  address borrowerAddress = ownerOf(_tokenId);
                  require(borrowerAddress != msg.sender, "MUST_OWN_TOKEN");
                  require(
                      state.data.tokenOwnersOnLoan[_tokenId] == msg.sender,
                      "MUST_OWN_TOKEN"
                  );
                  // Remove it from the array of loaned out tokens
                  delete state.data.tokenOwnersOnLoan[_tokenId];
                  // Subtract from the owner's loan balance
                  state.data.totalLoanedPerAddress[msg.sender] -= 1;
                  state.data.currentLoanTotal -= 1;
                  // Transfer the token back
                  _directApproveMsgSenderFor(_tokenId);
                  safeTransferFrom(borrowerAddress, msg.sender, _tokenId);
                  emit LoanRetrieved(borrowerAddress, msg.sender, _tokenId);
              }
              /**
               * @notice Allow contract owner to retrieve a loan to prevent malicious floor listings
               * @param _tokenId The id of the token to retrieve
               */
              function adminRetrieveLoan(uint256 _tokenId) external onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  address borrowerAddress = ownerOf(_tokenId);
                  address loanerAddress = state.data.tokenOwnersOnLoan[_tokenId];
                  require(loanerAddress != address(0), "TOKEN_NOT_LOANED");
                  // Remove it from the array of loaned out tokens
                  delete state.data.tokenOwnersOnLoan[_tokenId];
                  // Subtract from the owner's loan balance
                  state.data.totalLoanedPerAddress[loanerAddress] -= 1;
                  state.data.currentLoanTotal -= 1;
                  // Transfer the token back
                  _directApproveMsgSenderFor(_tokenId);
                  safeTransferFrom(borrowerAddress, loanerAddress, _tokenId);
                  emit LoanRetrieved(borrowerAddress, loanerAddress, _tokenId);
              }
              /**
               * Returns the total number of loaned tokens
               */
              function totalLoaned() public view returns (uint256) {
                  return HeyMintStorage.state().data.currentLoanTotal;
              }
              /**
               * Returns the loaned balance of an address
               * @param _owner The address to check
               */
              function loanedBalanceOf(address _owner) public view returns (uint256) {
                  return HeyMintStorage.state().data.totalLoanedPerAddress[_owner];
              }
              /**
               * Returns all the token ids loaned by a given address
               * @param _owner The address to check
               */
              function loanedTokensByAddress(
                  address _owner
              ) external view returns (uint256[] memory) {
                  uint256 totalTokensLoaned = loanedBalanceOf(_owner);
                  uint256 mintedSoFar = totalSupply();
                  uint256 tokenIdsIdx = 0;
                  uint256[] memory allTokenIds = new uint256[](totalTokensLoaned);
                  for (
                      uint256 i = 1;
                      i <= mintedSoFar && tokenIdsIdx != totalTokensLoaned;
                      i++
                  ) {
                      if (HeyMintStorage.state().data.tokenOwnersOnLoan[i] == _owner) {
                          allTokenIds[tokenIdsIdx] = i;
                          tokenIdsIdx++;
                      }
                  }
                  return allTokenIds;
              }
              // ============ REFUND ============
              /**
               * @notice Returns the refund price in wei. Refund price is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function refundPriceInWei() public view returns (uint256) {
                  return uint256(HeyMintStorage.state().advCfg.refundPrice) * 10 ** 13;
              }
              /**
               * Will return true if token holders can still return their tokens for a refund
               */
              function refundGuaranteeActive() public view returns (bool) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  return block.timestamp < state.advCfg.refundEndsAt;
              }
              /**
               * @notice Set the address where tokens are sent when refunded
               * @param _refundAddress The new refund address
               */
              function setRefundAddress(address _refundAddress) external onlyOwner {
                  require(_refundAddress != address(0), "CANNOT_SEND_TO_ZERO_ADDRESS");
                  HeyMintStorage.state().advCfg.refundAddress = _refundAddress;
              }
              /**
               * @notice Increase the period of time where token holders can still return their tokens for a refund
               * @param _newRefundEndsAt The new timestamp when the refund period ends. Must be greater than the current timestamp
               */
              function increaseRefundEndsAt(uint32 _newRefundEndsAt) external onlyOwner {
                  AdvancedConfig storage advCfg = HeyMintStorage.state().advCfg;
                  require(
                      _newRefundEndsAt > advCfg.refundEndsAt,
                      "MUST_INCREASE_DURATION"
                  );
                  HeyMintStorage.state().advCfg.refundEndsAt = _newRefundEndsAt;
              }
              /**
               * @notice Refund token and return the refund price to the token owner.
               * @param _tokenId The id of the token to refund
               */
              function refund(uint256 _tokenId) external nonReentrant {
                  require(refundGuaranteeActive(), "REFUND_GUARANTEE_EXPIRED");
                  require(ownerOf(_tokenId) == msg.sender, "MUST_OWN_TOKEN");
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  // In case refunds are enabled with conditional funding, don't allow burnToRefund on refunded tokens
                  if (state.cfg.fundingEndsAt > 0) {
                      delete state.data.pricePaid[_tokenId];
                  }
                  address addressToSendToken = state.advCfg.refundAddress != address(0)
                      ? state.advCfg.refundAddress
                      : owner();
                  safeTransferFrom(msg.sender, addressToSendToken, _tokenId);
                  (bool success, ) = payable(msg.sender).call{value: refundPriceInWei()}(
                      ""
                  );
                  require(success, "TRANSFER_FAILED");
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          /**
           * @title HeyMint ERC721A Function Reference
           * @author HeyMint Launchpad (https://join.heymint.xyz)
           * @notice This is a function reference contract for Etherscan reference purposes only.
           * This contract includes all the functions from multiple implementation contracts.
           */
          contract HeyMintERC721AReference {
              struct BaseConfig {
                  bool publicSaleActive;
                  bool usePublicSaleTimes;
                  bool presaleActive;
                  bool usePresaleTimes;
                  bool soulbindingActive;
                  bool randomHashActive;
                  bool enforceRoyalties;
                  bool heyMintFeeActive;
                  uint8 publicMintsAllowedPerAddress;
                  uint8 presaleMintsAllowedPerAddress;
                  uint8 publicMintsAllowedPerTransaction;
                  uint8 presaleMintsAllowedPerTransaction;
                  uint16 maxSupply;
                  uint16 presaleMaxSupply;
                  uint16 royaltyBps;
                  uint32 publicPrice;
                  uint32 presalePrice;
                  uint24 projectId;
                  string uriBase;
                  address presaleSignerAddress;
                  uint32 publicSaleStartTime;
                  uint32 publicSaleEndTime;
                  uint32 presaleStartTime;
                  uint32 presaleEndTime;
                  uint32 fundingEndsAt;
                  uint32 fundingTarget;
              }
              struct TokenOwnership {
                  address addr;
                  uint64 startTimestamp;
                  bool burned;
                  uint24 extraData;
              }
              struct AdvancedConfig {
                  bool stakingActive;
                  bool loaningActive;
                  bool freeClaimActive;
                  uint8 mintsPerFreeClaim;
                  address freeClaimContractAddress;
                  bool burnClaimActive;
                  bool useBurnTokenIdForMetadata;
                  uint8 mintsPerBurn;
                  uint32 burnPayment;
                  bool payoutAddressesFrozen;
                  uint32 refundEndsAt;
                  uint32 refundPrice;
                  bool metadataFrozen;
                  bool soulbindAdminTransfersPermanentlyDisabled;
                  bool depositClaimActive;
                  uint32 remainingDepositPayment;
                  address depositContractAddress;
                  bytes32 depositMerkleRoot;
                  uint16[] payoutBasisPoints;
                  address[] payoutAddresses;
                  address royaltyPayoutAddress;
                  address soulboundAdminAddress;
                  address refundAddress;
                  address creditCardMintAddress;
              }
              struct BurnToken {
                  address contractAddress;
                  uint8 tokenType;
                  uint8 tokensPerBurn;
                  uint16 tokenId;
              }
              function CORI_SUBSCRIPTION_ADDRESS() external view returns (address) {}
              function EMPTY_SUBSCRIPTION_ADDRESS() external view returns (address) {}
              function approve(address to, uint256 tokenId) external payable {}
              function balanceOf(address _owner) external view returns (uint256) {}
              function explicitOwnershipOf(
                  uint256 tokenId
              ) external view returns (TokenOwnership memory) {}
              function explicitOwnershipsOf(
                  uint256[] memory tokenIds
              ) external view returns (TokenOwnership[] memory) {}
              function freezeMetadata() external {}
              function getApproved(uint256 tokenId) external view returns (address) {}
              function defaultHeymintFeePerToken() external view returns (uint256) {}
              function heymintFeePerToken() external view returns (uint256) {}
              function setHeymintFeePerToken(uint256 _heymintFeePerToken) external {}
              function heymintPayoutAddress() external view returns (address) {}
              function initialize(
                  string memory _name,
                  string memory _symbol,
                  BaseConfig memory _config
              ) external {}
              function isApprovedForAll(
                  address _owner,
                  address operator
              ) external view returns (bool) {}
              function isOperatorFilterRegistryRevoked() external view returns (bool) {}
              function name() external view returns (string memory) {}
              function numberMinted(address _owner) external view returns (uint256) {}
              function owner() external view returns (address) {}
              function ownerOf(uint256 tokenId) external view returns (address) {}
              function pause() external {}
              function paused() external view returns (bool) {}
              function publicMint(uint256 _numTokens) external payable {}
              function publicPriceInWei() external view returns (uint256) {}
              function publicSaleTimeIsActive() external view returns (bool) {}
              function refundGuaranteeActive() external view returns (bool) {}
              function renounceOwnership() external {}
              function revokeOperatorFilterRegistry() external {}
              function royaltyInfo(
                  uint256,
                  uint256 _salePrice
              ) external view returns (address, uint256) {}
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable {}
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              ) external payable {}
              function setApprovalForAll(address operator, bool approved) external {}
              function setBaseURI(string memory _newBaseURI) external {}
              function setPublicMintsAllowedPerAddress(uint8 _mintsAllowed) external {}
              function setPublicMintsAllowedPerTransaction(
                  uint8 _mintsAllowed
              ) external {}
              function setPublicPrice(uint32 _publicPrice) external {}
              function setPublicSaleEndTime(uint32 _publicSaleEndTime) external {}
              function setPublicSaleStartTime(uint32 _publicSaleStartTime) external {}
              function setPublicSaleState(bool _saleActiveState) external {}
              function setUsePublicSaleTimes(bool _usePublicSaleTimes) external {}
              function setUser(uint256 tokenId, address user, uint64 expires) external {}
              function supportsInterface(
                  bytes4 interfaceId
              ) external view returns (bool) {}
              function symbol() external view returns (string memory) {}
              function tokenURI(uint256 tokenId) external view returns (string memory) {}
              function tokensOfOwner(
                  address _owner
              ) external view returns (uint256[] memory) {}
              function tokensOfOwnerIn(
                  address _owner,
                  uint256 start,
                  uint256 stop
              ) external view returns (uint256[] memory) {}
              function totalSupply() external view returns (uint256) {}
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable {}
              function transferOwnership(address newOwner) external {}
              function unpause() external {}
              function userExpires(uint256 tokenId) external view returns (uint256) {}
              function userOf(uint256 tokenId) external view returns (address) {}
              function withdraw() external {}
              function freezePayoutAddresses() external {}
              function getSettings()
                  external
                  view
                  returns (
                      BaseConfig memory,
                      AdvancedConfig memory,
                      BurnToken[] memory,
                      bool,
                      bool,
                      bool,
                      uint256
                  )
              {}
              function gift(
                  address[] memory _receivers,
                  uint256[] memory _mintNumber
              ) external payable {}
              function reduceMaxSupply(uint16 _newMaxSupply) external {}
              function setRoyaltyBasisPoints(uint16 _royaltyBps) external {}
              function setRoyaltyPayoutAddress(address _royaltyPayoutAddress) external {}
              function updateAdvancedConfig(
                  AdvancedConfig memory _advancedConfig
              ) external {}
              function updateBaseConfig(BaseConfig memory _baseConfig) external {}
              function updatePayoutAddressesAndBasisPoints(
                  address[] memory _payoutAddresses,
                  uint16[] memory _payoutBasisPoints
              ) external {}
              function heymintAdminAddress() external view returns (address) {}
              function burnAddress() external view returns (address) {}
              function burnToMint(
                  address[] memory _contracts,
                  uint256[][] memory _tokenIds,
                  uint256 _tokensToMint
              ) external payable {}
              function presaleMint(
                  bytes32 _messageHash,
                  bytes memory _signature,
                  uint256 _numTokens,
                  uint256 _maximumAllowedMints
              ) external payable {}
              function presalePriceInWei() external view returns (uint256) {}
              function presaleTimeIsActive() external view returns (bool) {}
              function reducePresaleMaxSupply(uint16 _newPresaleMaxSupply) external {}
              function setBurnClaimState(bool _burnClaimActive) external {}
              function setPresaleEndTime(uint32 _presaleEndTime) external {}
              function setPresaleMintsAllowedPerAddress(uint8 _mintsAllowed) external {}
              function setPresaleMintsAllowedPerTransaction(
                  uint8 _mintsAllowed
              ) external {}
              function setPresalePrice(uint32 _presalePrice) external {}
              function setPresaleSignerAddress(address _presaleSignerAddress) external {}
              function setPresaleStartTime(uint32 _presaleStartTime) external {}
              function setPresaleState(bool _saleActiveState) external {}
              function setUseBurnTokenIdForMetadata(
                  bool _useBurnTokenIdForMetadata
              ) external {}
              function setUsePresaleTimes(bool _usePresaleTimes) external {}
              function updateBurnTokens(BurnToken[] memory _burnTokens) external {}
              function updateMintsPerBurn(uint8 _mintsPerBurn) external {}
              function adminUnstake(uint256 _tokenId) external {}
              function baseTokenURI() external view returns (string memory) {}
              function checkFreeClaimEligibility(
                  uint256[] memory _tokenIDs
              ) external view returns (bool[] memory) {}
              function currentTokenStakeTime(
                  uint256 _tokenId
              ) external view returns (uint256) {}
              function disableSoulbindAdminTransfersPermanently() external {}
              function freeClaim(uint256[] memory _tokenIDs) external payable {}
              function getRandomHashes(
                  uint256[] memory _tokenIDs
              ) external view returns (bytes32[] memory) {}
              function setFreeClaimContractAddress(
                  address _freeClaimContractAddress
              ) external {}
              function setFreeClaimState(bool _freeClaimActive) external {}
              function setGenerateRandomHashState(bool _randomHashActive) external {}
              function setSoulbindingState(bool _soulbindingActive) external {}
              function setSoulboundAdminAddress(address _adminAddress) external {}
              function setStakingState(bool _stakingState) external {}
              function setTokenURIs(
                  uint256[] memory _tokenIds,
                  string[] memory _newURIs
              ) external {}
              function soulboundAdminTransfer(
                  address _from,
                  address _to,
                  uint256 _tokenId
              ) external {}
              function stakeTokens(uint256[] memory _tokenIds) external {}
              function stakingTransfer(
                  address _from,
                  address _to,
                  uint256 _tokenId
              ) external {}
              function totalTokenStakeTime(
                  uint256 _tokenId
              ) external view returns (uint256) {}
              function unstakeTokens(uint256[] memory _tokenIds) external {}
              function updateMintsPerFreeClaim(uint8 _mintsPerFreeClaim) external {}
              function adminRetrieveLoan(uint256 _tokenId) external {}
              function burnDepositTokensToMint(
                  uint256[] memory _tokenIds,
                  bytes32[][] memory _merkleProofs
              ) external payable {}
              function burnToRefund(uint256[] memory _tokenIds) external {}
              function determineFundingSuccess() external {}
              function fundingTargetInWei() external view returns (uint256) {}
              function increaseRefundEndsAt(uint32 _newRefundEndsAt) external {}
              function loan(uint256 _tokenId, address _receiver) external {}
              function loanedBalanceOf(address _owner) external view returns (uint256) {}
              function loanedTokensByAddress(
                  address _owner
              ) external view returns (uint256[] memory) {}
              function refund(uint256 _tokenId) external {}
              function refundPriceInWei() external view returns (uint256) {}
              function remainingDepositPaymentInWei() external view returns (uint256) {}
              function retrieveLoan(uint256 _tokenId) external {}
              function setDepositClaimState(bool _depositClaimActive) external {}
              function setDepositContractAddress(
                  address _depositContractAddress
              ) external {}
              function setDepositMerkleRoot(bytes32 _depositMerkleRoot) external {}
              function setLoaningActive(bool _loaningActive) external {}
              function setRefundAddress(address _refundAddress) external {}
              function setRemainingDepositPayment(
                  uint32 _remainingDepositPayment
              ) external {}
              function totalLoaned() external view returns (uint256) {}
              function burnPaymentInWei() external view returns (uint256) {}
              function updatePaymentPerBurn(uint32 _burnPayment) external {}
              function setCreditCardMintAddress(
                  address _creditCardMintAddress
              ) external {}
              function creditCardMint(uint256 _numTokens, address _to) external payable {}
              function getDefaultCreditCardMintAddresses()
                  public
                  pure
                  returns (address[5] memory)
              {}
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {Data, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          import {ERC721AUpgradeable, IERC721AUpgradeable, ERC721AStorage} from "erc721a-upgradeable/contracts/ERC721AUpgradeable.sol";
          import {ERC4907AUpgradeable} from "erc721a-upgradeable/contracts/extensions/ERC4907AUpgradeable.sol";
          import {ERC721AQueryableUpgradeable} from "erc721a-upgradeable/contracts/extensions/ERC721AQueryableUpgradeable.sol";
          import {IERC2981Upgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
          import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
          import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
          import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import {RevokableOperatorFiltererUpgradeable} from "operator-filter-registry/src/upgradeable/RevokableOperatorFiltererUpgradeable.sol";
          /**
           * @title HeyMintERC721AUpgradeable
           * @author HeyMint Launchpad (https://join.heymint.xyz)
           * @notice This contract contains shared logic to be inherited by all implementation contracts
           */
          contract HeyMintERC721AUpgradeable is
              ERC4907AUpgradeable,
              ERC721AQueryableUpgradeable,
              OwnableUpgradeable,
              PausableUpgradeable,
              ReentrancyGuardUpgradeable,
              RevokableOperatorFiltererUpgradeable
          {
              using HeyMintStorage for HeyMintStorage.State;
              uint256 public constant defaultHeymintFeePerToken = 0.0007 ether;
              address public constant heymintPayoutAddress =
                  0xE1FaC470dE8dE91c66778eaa155C64c7ceEFc851;
              // ============ BASE FUNCTIONALITY ============
              /**
               * @dev Overrides the default ERC721A _startTokenId() so tokens begin at 1 instead of 0
               */
              function _startTokenId() internal view virtual override returns (uint256) {
                  return 1;
              }
              /**
               * @notice Wraps and exposes publicly _numberMinted() from ERC721A
               * @param _owner The address of the owner to check
               */
              function numberMinted(address _owner) public view returns (uint256) {
                  return _numberMinted(_owner);
              }
              /**
               * @dev Used to directly approve a token for transfers by the current msg.sender,
               * bypassing the typical checks around msg.sender being the owner of a given token.
               * This approval will be automatically deleted once the token is transferred.
               * @param _tokenId The ID of the token to approve
               */
              function _directApproveMsgSenderFor(uint256 _tokenId) internal {
                  ERC721AStorage.layout()._tokenApprovals[_tokenId].value = msg.sender;
              }
              /**
               * @notice Returns the owner of the contract
               */
              function owner()
                  public
                  view
                  virtual
                  override(OwnableUpgradeable, RevokableOperatorFiltererUpgradeable)
                  returns (address)
              {
                  return OwnableUpgradeable.owner();
              }
              // https://chiru-labs.github.io/ERC721A/#/migration?id=supportsinterface
              /**
               * @notice Returns true if the contract implements the interface defined by interfaceId
               * @param interfaceId The interface identifier, as specified in ERC-165
               */
              function supportsInterface(
                  bytes4 interfaceId
              )
                  public
                  view
                  virtual
                  override(ERC721AUpgradeable, IERC721AUpgradeable, ERC4907AUpgradeable)
                  returns (bool)
              {
                  // Supports the following interfaceIds:
                  // - IERC165: 0x01ffc9a7
                  // - IERC721: 0x80ac58cd
                  // - IERC721Metadata: 0x5b5e139f
                  // - IERC2981: 0x2a55205a
                  // - IERC4907: 0xad092b5c
                  return
                      ERC721AUpgradeable.supportsInterface(interfaceId) ||
                      type(IERC2981Upgradeable).interfaceId == interfaceId ||
                      ERC4907AUpgradeable.supportsInterface(interfaceId);
              }
              // ============ HEYMINT FEE ============
              /**
               * @notice Returns the HeyMint fee per token. If the fee is 0, the default fee is returned
               */
              function heymintFeePerToken() public view returns (uint256) {
                  uint256 fee = HeyMintStorage.state().data.heymintFeePerToken;
                  return fee == 0 ? defaultHeymintFeePerToken : fee;
              }
              // ============ OPERATOR FILTER REGISTRY ============
              /**
               * @notice Override default ERC-721 setApprovalForAll to require that the operator is not from a blocklisted exchange
               * @dev See {IERC721-setApprovalForAll}.
               * @param operator Address to add to the set of authorized operators
               * @param approved True if the operator is approved, false to revoke approval
               */
              function setApprovalForAll(
                  address operator,
                  bool approved
              )
                  public
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperatorApproval(operator)
              {
                  require(
                      !HeyMintStorage.state().cfg.soulbindingActive,
                      "TOKEN_IS_SOULBOUND"
                  );
                  super.setApprovalForAll(operator, approved);
              }
              /**
               * @notice Override default ERC721 approve to require that the operator is not from a blocklisted exchange
               * @dev See {IERC721-approve}.
               * @param to Address to receive the approval
               * @param tokenId ID of the token to be approved
               */
              function approve(
                  address to,
                  uint256 tokenId
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperatorApproval(to)
              {
                  require(
                      !HeyMintStorage.state().cfg.soulbindingActive,
                      "TOKEN_IS_SOULBOUND"
                  );
                  super.approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-transferFrom}.
               *      The added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperator(from)
              {
                  super.transferFrom(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               *      The added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperator(from)
              {
                  super.safeTransferFrom(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               *      The added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperator(from)
              {
                  super.safeTransferFrom(from, to, tokenId, data);
              }
              // ============ RANDOM HASH ============
              /**
               * @notice Generate a suitably random hash from block data
               * Can be used later to determine any sort of arbitrary outcome
               * @param _tokenId The token ID to generate a random hash for
               */
              function _generateRandomHash(uint256 _tokenId) internal {
                  Data storage data = HeyMintStorage.state().data;
                  if (data.randomHashStore[_tokenId] == bytes32(0)) {
                      data.randomHashStore[_tokenId] = keccak256(
                          abi.encode(block.prevrandao, _tokenId)
                      );
                  }
              }
              // ============ TOKEN TRANSFER CHECKS ============
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 tokenId,
                  uint256 quantity
              ) internal override whenNotPaused onlyAllowedOperator(from) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(
                      !state.advCfg.stakingActive ||
                          state.data.stakingTransferActive ||
                          state.data.currentTimeStaked[tokenId] == 0,
                      "TOKEN_IS_STAKED"
                  );
                  require(
                      state.data.tokenOwnersOnLoan[tokenId] == address(0),
                      "CANNOT_TRANSFER_LOANED_TOKEN"
                  );
                  if (
                      state.cfg.soulbindingActive &&
                      !state.data.soulboundAdminTransferInProgress
                  ) {
                      require(from == address(0), "TOKEN_IS_SOULBOUND");
                  }
                  if (state.cfg.randomHashActive && from == address(0)) {
                      _generateRandomHash(tokenId);
                  }
                  super._beforeTokenTransfers(from, to, tokenId, quantity);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          struct Implementation {
              address implAddress;
              bytes4[] selectors;
          }
          interface IAddressRelay {
              /**
               * @notice Returns the fallback implementation address
               */
              function fallbackImplAddress() external returns (address);
              /**
               * @notice Adds or updates selectors and their implementation addresses
               * @param _selectors The selectors to add or update
               * @param _implAddress The implementation address the selectors will point to
               */
              function addOrUpdateSelectors(
                  bytes4[] memory _selectors,
                  address _implAddress
              ) external;
              /**
               * @notice Removes selectors
               * @param _selectors The selectors to remove
               */
              function removeSelectors(bytes4[] memory _selectors) external;
              /**
               * @notice Removes an implementation address and all the selectors that point to it
               * @param _implAddress The implementation address to remove
               */
              function removeImplAddressAndAllSelectors(address _implAddress) external;
              /**
               * @notice Returns the implementation address for a given function selector
               * @param _functionSelector The function selector to get the implementation address for
               */
              function getImplAddress(
                  bytes4 _functionSelector
              ) external view returns (address implAddress_);
              /**
               * @notice Returns all the implementation addresses and the selectors they support
               * @return impls_ An array of Implementation structs
               */
              function getAllImplAddressesAndSelectors()
                  external
                  view
                  returns (Implementation[] memory impls_);
              /**
               * @notice Return all the fucntion selectors associated with an implementation address
               * @param _implAddress The implementation address to get the selectors for
               */
              function getSelectorsForImplAddress(
                  address _implAddress
              ) external view returns (bytes4[] memory selectors_);
              /**
               * @notice Sets the fallback implementation address to use when a function selector is not found
               * @param _fallbackAddress The fallback implementation address
               */
              function setFallbackImplAddress(address _fallbackAddress) external;
              /**
               * @notice Updates the supported interfaces
               * @param _interfaceId The interface ID to update
               * @param _supported Whether the interface is supported or not
               */
              function updateSupportedInterfaces(
                  bytes4 _interfaceId,
                  bool _supported
              ) external;
              /**
               * @notice Returns whether the interface is supported or not
               * @param _interfaceId The interface ID to check
               */
              function supportsInterface(
                  bytes4 _interfaceId
              ) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          interface IERC165 {
              /// @notice Query if a contract implements an interface
              /// @param interfaceId The interface identifier, as specified in ERC-165
              /// @dev Interface identification is specified in ERC-165. This function
              ///  uses less than 30,000 gas.
              /// @return `true` if the contract implements `interfaceID` and
              ///  `interfaceID` is not 0xffffffff, `false` otherwise
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @title ERC-173 Contract Ownership Standard
          ///  Note: the ERC-165 identifier for this interface is 0x7f5828d0
          /* is ERC165 */
          interface IERC173 {
              /// @dev This emits when ownership of a contract changes.
              event OwnershipTransferred(
                  address indexed previousOwner,
                  address indexed newOwner
              );
              /// @notice Get the address of the owner
              /// @return owner_ The address of the owner.
              function owner() external view returns (address owner_);
              /// @notice Set the address of the new owner of the contract
              /// @dev Set _newOwner to address(0) to renounce any ownership.
              /// @param _newOwner The address of the new owner of the contract
              function transferOwnership(address _newOwner) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          interface IExchangeOperatorAddressList {
              /**
               * @notice Returns an integer representing the exchange a given operator address belongs to (0 if none)
               * @param _operatorAddress The operator address to map to an exchange
               */
              function operatorAddressToExchange(
                  address _operatorAddress
              ) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          struct BaseConfig {
              // If true tokens can be minted in the public sale
              bool publicSaleActive;
              // If enabled, automatic start and stop times for the public sale will be enforced, otherwise ignored
              bool usePublicSaleTimes;
              // If true tokens can be minted in the presale
              bool presaleActive;
              // If enabled, automatic start and stop times for the presale will be enforced, otherwise ignored
              bool usePresaleTimes;
              // If true, all tokens will be soulbound
              bool soulbindingActive;
              // If true, a random hash will be generated for each token
              bool randomHashActive;
              // If true, the default CORI subscription address will be used to enforce royalties with the Operator Filter Registry
              bool enforceRoyalties;
              // If true, HeyMint fees will be charged for minting tokens
              bool heyMintFeeActive;
              // The number of tokens that can be minted in the public sale per address
              uint8 publicMintsAllowedPerAddress;
              // The number of tokens that can be minted in the presale per address
              uint8 presaleMintsAllowedPerAddress;
              // The number of tokens that can be minted in the public sale per transaction
              uint8 publicMintsAllowedPerTransaction;
              // The number of tokens that can be minted in the presale sale per transaction
              uint8 presaleMintsAllowedPerTransaction;
              // Maximum supply of tokens that can be minted
              uint16 maxSupply;
              // Total number of tokens available for minting in the presale
              uint16 presaleMaxSupply;
              // The royalty payout percentage in basis points
              uint16 royaltyBps;
              // The price of a token in the public sale in 1/100,000 ETH - e.g. 1 = 0.00001 ETH, 100,000 = 1 ETH - multiply by 10^13 to get correct wei amount
              uint32 publicPrice;
              // The price of a token in the presale in 1/100,000 ETH
              uint32 presalePrice;
              // Used to create a default HeyMint Launchpad URI for token metadata to save gas over setting a custom URI and increase fetch reliability
              uint24 projectId;
              // The base URI for all token metadata
              string uriBase;
              // The address used to sign and validate presale mints
              address presaleSignerAddress;
              // The automatic start time for the public sale (if usePublicSaleTimes is true and publicSaleActive is true)
              uint32 publicSaleStartTime;
              // The automatic end time for the public sale (if usePublicSaleTimes is true and publicSaleActive is true)
              uint32 publicSaleEndTime;
              // The automatic start time for the presale (if usePresaleTimes is true and presaleActive is true)
              uint32 presaleStartTime;
              // The automatic end time for the presale (if usePresaleTimes is true and presaleActive is true)
              uint32 presaleEndTime;
              // If set, the UTC timestamp in seconds by which the fundingTarget must be met or funds are refundable
              uint32 fundingEndsAt;
              // The amount of centiETH that must be raised by fundingEndsAt or funds are refundable - multiply by 10^16
              uint32 fundingTarget;
          }
          struct AdvancedConfig {
              // When false, tokens cannot be staked but can still be unstaked
              bool stakingActive;
              // When false, tokens cannot be loaned but can still be retrieved
              bool loaningActive;
              // If true tokens can be claimed for free
              bool freeClaimActive;
              // The number of tokens that can be minted per free claim
              uint8 mintsPerFreeClaim;
              // Optional address of an NFT that is eligible for free claim
              address freeClaimContractAddress;
              // If true tokens can be burned in order to mint
              bool burnClaimActive;
              // If true, the original token id of a burned token will be used for metadata
              bool useBurnTokenIdForMetadata;
              // The number of tokens that can be minted per burn transaction
              uint8 mintsPerBurn;
              // The payment required alongside a burn transaction in order to mint in 1/100,000 ETH
              uint32 burnPayment;
              // Permanently freezes payout addresses and basis points so they can never be updated
              bool payoutAddressesFrozen;
              // If set, the UTC timestamp in seconds until which tokens are refundable for refundPrice
              uint32 refundEndsAt;
              // The amount returned to a user in a token refund in 1/100,000 ETH
              uint32 refundPrice;
              // Permanently freezes metadata so it can never be changed
              bool metadataFrozen;
              // If true the soulbind admin address is permanently disabled
              bool soulbindAdminTransfersPermanentlyDisabled;
              // If true deposit tokens can be burned in order to mint
              bool depositClaimActive;
              // If additional payment is required to mint, this is the amount required in centiETH
              uint32 remainingDepositPayment;
              // The deposit token smart contract address
              address depositContractAddress;
              // The merkle root used to validate if deposit tokens are eligible to burn to mint
              bytes32 depositMerkleRoot;
              // The respective share of funds to be sent to each address in payoutAddresses in basis points
              uint16[] payoutBasisPoints;
              // The addresses to which funds are sent when a token is sold. If empty, funds are sent to the contract owner.
              address[] payoutAddresses;
              // Optional address where royalties are paid out. If not set, royalties are paid to the contract owner.
              address royaltyPayoutAddress;
              // Used to allow transferring soulbound tokens with admin privileges. Defaults to the contract owner if not set.
              address soulboundAdminAddress;
              // The address where refunded tokens are returned. If not set, refunded tokens are sent to the contract owner.
              address refundAddress;
              // An address authorized to call the creditCardMint function.
              address creditCardMintAddress;
          }
          struct BurnToken {
              // The contract address of the token to be burned
              address contractAddress;
              // The type of contract - 1 = ERC-721, 2 = ERC-1155
              uint8 tokenType;
              // The number of tokens to burn per mint
              uint8 tokensPerBurn;
              // The ID of the token on an ERC-1155 contract eligible for burn; unused for ERC-721
              uint16 tokenId;
          }
          struct Data {
              // ============ BASE FUNCTIONALITY ============
              // HeyMint fee to be paid per minted token (if not set, defaults to defaultHeymintFeePerToken)
              uint256 heymintFeePerToken;
              // Keeps track of if advanced config settings have been initialized to prevent setting multiple times
              bool advancedConfigInitialized;
              // A mapping of token IDs to specific tokenURIs for tokens that have custom metadata
              mapping(uint256 => string) tokenURIs;
              // ============ CONDITIONAL FUNDING ============
              // If true, the funding target was reached and funds are not refundable
              bool fundingTargetReached;
              // If true, funding success has been determined and determineFundingSuccess() can no longer be called
              bool fundingSuccessDetermined;
              // A mapping of token ID to price paid for the token
              mapping(uint256 => uint256) pricePaid;
              // ============ SOULBINDING ============
              // Used to allow an admin to transfer soulbound tokens when necessary
              bool soulboundAdminTransferInProgress;
              // ============ BURN TO MINT ============
              // Maps a token id to the burn token id that was used to mint it to match metadata
              mapping(uint256 => uint256) tokenIdToBurnTokenId;
              // ============ STAKING ============
              // Used to allow direct transfers of staked tokens without unstaking first
              bool stakingTransferActive;
              // Returns the UNIX timestamp at which a token began staking if currently staked
              mapping(uint256 => uint256) currentTimeStaked;
              // Returns the total time a token has been staked in seconds, not counting the current staking time if any
              mapping(uint256 => uint256) totalTimeStaked;
              // ============ LOANING ============
              // Used to keep track of the total number of tokens on loan
              uint256 currentLoanTotal;
              // Returns the total number of tokens loaned by an address
              mapping(address => uint256) totalLoanedPerAddress;
              // Returns the address of the original token owner if a token is currently on loan
              mapping(uint256 => address) tokenOwnersOnLoan;
              // ============ FREE CLAIM ============
              // If true token has already been used to claim and cannot be used again
              mapping(uint256 => bool) freeClaimUsed;
              // ============ RANDOM HASH ============
              // Stores a random hash for each token ID
              mapping(uint256 => bytes32) randomHashStore;
          }
          library HeyMintStorage {
              struct State {
                  BaseConfig cfg;
                  AdvancedConfig advCfg;
                  BurnToken[] burnTokens;
                  Data data;
              }
              bytes32 internal constant STORAGE_SLOT =
                  keccak256("heymint.launchpad.storage.erc721a");
              function state() internal pure returns (State storage s) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      s.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.17;
          import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
          import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
          import "@openzeppelin/contracts/access/Ownable.sol";
          import "@openzeppelin/contracts/security/Pausable.sol";
          import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import "@openzeppelin/contracts/token/common/ERC2981.sol";
          /**
           * @author Created with HeyMint Launchpad https://launchpad.heymint.xyz
           * @notice This contract handles minting Verification test tokens.
           */
          contract EnumerableERC1155 is
              ERC1155Supply,
              Ownable,
              Pausable,
              ReentrancyGuard,
              ERC2981
          {
              using ECDSA for bytes32;
              // Used to validate authorized presale mint addresses
              address private presaleSignerAddress =
                  0x0fE6E0D15E6F775138Ab556dE54B96d5C1358F3D;
              address public royaltyAddress = 0x7A4dF7B461f1bE3e88373a4d933aeefE2FAdcE71;
              address[] public payoutAddresses = [
                  0xD3371FD388664Bd16A267788dbE977582B850f5b
              ];
              // Permanently freezes metadata for all tokens so they can never be changed
              bool public allMetadataFrozen = false;
              // If true, payout addresses and basis points are permanently frozen and can never be updated
              bool public payoutAddressesFrozen;
              // The amount of tokens minted by a given address for a given token id
              mapping(address => mapping(uint256 => uint256))
                  public tokensMintedByAddress;
              // Permanently freezes metadata for a specific token id so it can never be changed
              mapping(uint256 => bool) public tokenMetadataFrozen;
              // If true, the given token id can never be minted again
              mapping(uint256 => bool) public tokenMintingPermanentlyDisabled;
              mapping(uint256 => bool) public tokenPresaleSaleActive;
              mapping(uint256 => bool) public tokenPublicSaleActive;
              // If true, sale start and end times for the presale will be enforced, else ignored
              mapping(uint256 => bool) public tokenUsePresaleTimes;
              // If true, sale start and end times for the public sale will be enforced, else ignored
              mapping(uint256 => bool) public tokenUsePublicSaleTimes;
              mapping(uint256 => string) public tokenURI;
              // Maximum supply of tokens that can be minted for each token id. If zero, this token is open edition and has no mint limit
              mapping(uint256 => uint256) public tokenMaxSupply;
              // If zero, this token is open edition and has no mint limit
              mapping(uint256 => uint256) public tokenPresaleMaxSupply;
              mapping(uint256 => uint256) public tokenPresaleMintsPerAddress;
              mapping(uint256 => uint256) public tokenPresalePrice;
              mapping(uint256 => uint256) public tokenPresaleSaleEndTime;
              mapping(uint256 => uint256) public tokenPresaleSaleStartTime;
              mapping(uint256 => uint256) public tokenPublicMintsPerAddress;
              mapping(uint256 => uint256) public tokenPublicPrice;
              mapping(uint256 => uint256) public tokenPublicSaleEndTime;
              mapping(uint256 => uint256) public tokenPublicSaleStartTime;
              string public name = "Verification test";
              string public symbol = "VRT";
              // The respective share of funds to be sent to each address in payoutAddresses in basis points
              uint256[] public payoutBasisPoints = [10000];
              uint96 public royaltyFee = 0;
              constructor()
                  ERC1155(
                      "ipfs://bafybeicin4rmb5y44r2a5jhwvobfgxutabetrttoi3u2po7pdymxt7dwdy/{id}"
                  )
              {
                  _setDefaultRoyalty(royaltyAddress, royaltyFee);
                  tokenPublicPrice[1] = 0.1 ether;
                  tokenPublicMintsPerAddress[1] = 0;
                  require(
                      payoutAddresses.length == payoutBasisPoints.length,
                      "PAYOUT_ARRAYS_NOT_SAME_LENGTH"
                  );
                  uint256 totalPayoutBasisPoints = 0;
                  for (uint256 i = 0; i < payoutBasisPoints.length; i++) {
                      totalPayoutBasisPoints += payoutBasisPoints[i];
                  }
                  require(
                      totalPayoutBasisPoints == 10000,
                      "TOTAL_BASIS_POINTS_MUST_BE_10000"
                  );
              }
              modifier originalUser() {
                  require(tx.origin == msg.sender, "CANNOT_CALL_FROM_CONTRACT");
                  _;
              }
              /**
               * @notice Returns a custom URI for each token id if set
               */
              function uri(
                  uint256 _tokenId
              ) public view override returns (string memory) {
                  // If no URI exists for the specific id requested, fallback to the default ERC-1155 URI.
                  if (bytes(tokenURI[_tokenId]).length == 0) {
                      return super.uri(_tokenId);
                  }
                  return tokenURI[_tokenId];
              }
              /**
               * @notice Sets a URI for a specific token id.
               */
              function setURI(
                  uint256 _tokenId,
                  string calldata _newTokenURI
              ) external onlyOwner {
                  require(
                      !allMetadataFrozen && !tokenMetadataFrozen[_tokenId],
                      "METADATA_HAS_BEEN_FROZEN"
                  );
                  tokenURI[_tokenId] = _newTokenURI;
              }
              /**
               * @notice Update the global default ERC-1155 base URI
               */
              function setGlobalURI(string calldata _newTokenURI) external onlyOwner {
                  require(!allMetadataFrozen, "METADATA_HAS_BEEN_FROZEN");
                  _setURI(_newTokenURI);
              }
              /**
               * @notice Freeze metadata for a specific token id so it can never be changed again
               */
              function freezeTokenMetadata(uint256 _tokenId) external onlyOwner {
                  require(
                      !tokenMetadataFrozen[_tokenId],
                      "METADATA_HAS_ALREADY_BEEN_FROZEN"
                  );
                  tokenMetadataFrozen[_tokenId] = true;
              }
              /**
               * @notice Freeze all metadata so it can never be changed again
               */
              function freezeAllMetadata() external onlyOwner {
                  require(!allMetadataFrozen, "METADATA_HAS_ALREADY_BEEN_FROZEN");
                  allMetadataFrozen = true;
              }
              /**
               * @notice Reduce the max supply of tokens for a given token id
               * @param _newMaxSupply The new maximum supply of tokens available to mint
               * @param _tokenId The token id to reduce the max supply for
               */
              function reduceMaxSupply(
                  uint256 _tokenId,
                  uint256 _newMaxSupply
              ) external onlyOwner {
                  require(
                      tokenMaxSupply[_tokenId] == 0 ||
                          _newMaxSupply < tokenMaxSupply[_tokenId],
                      "NEW_MAX_SUPPLY_TOO_HIGH"
                  );
                  require(
                      _newMaxSupply >= totalSupply(_tokenId),
                      "SUPPLY_LOWER_THAN_MINTED_TOKENS"
                  );
                  tokenMaxSupply[_tokenId] = _newMaxSupply;
              }
              /**
               * @notice Lock a token id so that it can never be minted again
               */
              function permanentlyDisableTokenMinting(
                  uint256 _tokenId
              ) external onlyOwner {
                  tokenMintingPermanentlyDisabled[_tokenId] = true;
              }
              /**
               * @notice Change the royalty fee for the collection
               */
              function setRoyaltyFee(uint96 _feeNumerator) external onlyOwner {
                  royaltyFee = _feeNumerator;
                  _setDefaultRoyalty(royaltyAddress, royaltyFee);
              }
              /**
               * @notice Change the royalty address where royalty payouts are sent
               */
              function setRoyaltyAddress(address _royaltyAddress) external onlyOwner {
                  royaltyAddress = _royaltyAddress;
                  _setDefaultRoyalty(royaltyAddress, royaltyFee);
              }
              function pause() external onlyOwner {
                  _pause();
              }
              function unpause() external onlyOwner {
                  _unpause();
              }
              /**
               * @notice Override ERC1155 such that zero amount token transfers are disallowed.
               * This prevents arbitrary 'creation' of new tokens in the collection by anyone.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 id,
                  uint256 amount,
                  bytes memory data
              ) public override {
                  require(amount > 0, "AMOUNT_CANNOT_BE_ZERO");
                  return super.safeTransferFrom(from, to, id, amount, data);
              }
              function supportsInterface(
                  bytes4 _interfaceId
              ) public view override(ERC1155, ERC2981) returns (bool) {
                  return super.supportsInterface(_interfaceId);
              }
              /**
               * @notice Allow owner to send tokens without cost to multiple addresses
               */
              function giftTokens(
                  uint256 _tokenId,
                  address[] calldata _receivers,
                  uint256[] calldata _mintNumber
              ) external onlyOwner {
                  require(
                      !tokenMintingPermanentlyDisabled[_tokenId],
                      "MINTING_PERMANENTLY_DISABLED"
                  );
                  uint256 totalMint = 0;
                  for (uint256 i = 0; i < _mintNumber.length; i++) {
                      totalMint += _mintNumber[i];
                  }
                  // require either no tokenMaxSupply set or tokenMaxSupply not maxed out
                  require(
                      tokenMaxSupply[_tokenId] == 0 ||
                          totalSupply(_tokenId) + totalMint <= tokenMaxSupply[_tokenId],
                      "MINT_TOO_LARGE"
                  );
                  for (uint256 i = 0; i < _receivers.length; i++) {
                      _mint(_receivers[i], _tokenId, _mintNumber[i], "");
                  }
              }
              /**
               * @notice To be updated by contract owner to allow public sale minting for a given token
               */
              function setTokenPublicSaleState(
                  uint256 _tokenId,
                  bool _saleActiveState
              ) external onlyOwner {
                  require(
                      tokenPublicSaleActive[_tokenId] != _saleActiveState,
                      "NEW_STATE_IDENTICAL_TO_OLD_STATE"
                  );
                  tokenPublicSaleActive[_tokenId] = _saleActiveState;
              }
              /**
               * @notice Update the public mint price for a given token
               */
              function setTokenPublicPrice(
                  uint256 _tokenId,
                  uint256 _publicPrice
              ) external onlyOwner {
                  tokenPublicPrice[_tokenId] = _publicPrice;
              }
              /**
               * @notice Set the maximum public mints allowed per a given address for a given token
               */
              function setTokenPublicMintsAllowedPerAddress(
                  uint256 _tokenId,
                  uint256 _mintsAllowed
              ) external onlyOwner {
                  tokenPublicMintsPerAddress[_tokenId] = _mintsAllowed;
              }
              /**
               * @notice Update the start time for public mint for a given token
               */
              function setTokenPublicSaleStartTime(
                  uint256 _tokenId,
                  uint256 _publicSaleStartTime
              ) external onlyOwner {
                  require(_publicSaleStartTime > block.timestamp, "TIME_IN_PAST");
                  tokenPublicSaleStartTime[_tokenId] = _publicSaleStartTime;
              }
              /**
               * @notice Update the end time for public mint for a given token
               */
              function setTokenPublicSaleEndTime(
                  uint256 _tokenId,
                  uint256 _publicSaleEndTime
              ) external onlyOwner {
                  require(_publicSaleEndTime > block.timestamp, "TIME_IN_PAST");
                  tokenPublicSaleEndTime[_tokenId] = _publicSaleEndTime;
              }
              /**
               * @notice Update whether or not to use the automatic public sale times for a given token
               */
              function setTokenUsePublicSaleTimes(
                  uint256 _tokenId,
                  bool _usePublicSaleTimes
              ) external onlyOwner {
                  require(
                      tokenUsePublicSaleTimes[_tokenId] != _usePublicSaleTimes,
                      "NEW_STATE_IDENTICAL_TO_OLD_STATE"
                  );
                  tokenUsePublicSaleTimes[_tokenId] = _usePublicSaleTimes;
              }
              /**
               * @notice Returns if public sale times are active for a given token
               */
              function tokenPublicSaleTimeIsActive(
                  uint256 _tokenId
              ) public view returns (bool) {
                  if (tokenUsePublicSaleTimes[_tokenId] == false) {
                      return true;
                  }
                  return
                      block.timestamp >= tokenPublicSaleStartTime[_tokenId] &&
                      block.timestamp <= tokenPublicSaleEndTime[_tokenId];
              }
              /**
               * @notice Allow for public minting of tokens for a given token
               */
              function mintToken(
                  uint256 _tokenId,
                  uint256 _numTokens
              ) external payable originalUser nonReentrant {
                  require(tokenPublicSaleActive[_tokenId], "PUBLIC_SALE_IS_NOT_ACTIVE");
                  require(
                      tokenPublicSaleTimeIsActive(_tokenId),
                      "PUBLIC_SALE_TIME_IS_NOT_ACTIVE"
                  );
                  require(
                      tokenPublicMintsPerAddress[_tokenId] == 0 ||
                          tokensMintedByAddress[msg.sender][_tokenId] + _numTokens <=
                          tokenPublicMintsPerAddress[_tokenId],
                      "MAX_MINTS_FOR_ADDRESS_EXCEEDED"
                  );
                  require(
                      tokenMaxSupply[_tokenId] == 0 ||
                          totalSupply(_tokenId) + _numTokens <= tokenMaxSupply[_tokenId],
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  require(
                      msg.value == tokenPublicPrice[_tokenId] * _numTokens,
                      "PAYMENT_INCORRECT"
                  );
                  require(
                      !tokenMintingPermanentlyDisabled[_tokenId],
                      "MINTING_PERMANENTLY_DISABLED"
                  );
                  tokensMintedByAddress[msg.sender][_tokenId] += _numTokens;
                  _mint(msg.sender, _tokenId, _numTokens, "");
                  if (
                      tokenMaxSupply[_tokenId] != 0 &&
                      totalSupply(_tokenId) >= tokenMaxSupply[_tokenId]
                  ) {
                      tokenPublicSaleActive[_tokenId] = false;
                  }
              }
              /**
               * @notice Set the signer address used to verify presale minting
               */
              function setPresaleSignerAddress(
                  address _presaleSignerAddress
              ) external onlyOwner {
                  require(_presaleSignerAddress != address(0));
                  presaleSignerAddress = _presaleSignerAddress;
              }
              /**
               * @notice To be updated by contract owner to allow presale minting for a given token
               */
              function setTokenPresaleState(
                  uint256 _tokenId,
                  bool _saleActiveState
              ) external onlyOwner {
                  require(
                      tokenPresaleSaleActive[_tokenId] != _saleActiveState,
                      "NEW_STATE_IDENTICAL_TO_OLD_STATE"
                  );
                  tokenPresaleSaleActive[_tokenId] = _saleActiveState;
              }
              /**
               * @notice Update the presale mint price for a given token
               */
              function setTokenPresalePrice(
                  uint256 _tokenId,
                  uint256 _presalePrice
              ) external onlyOwner {
                  tokenPresalePrice[_tokenId] = _presalePrice;
              }
              /**
               * @notice Set the maximum presale mints allowed per a given address for a given token
               */
              function setTokenPresaleMintsAllowedPerAddress(
                  uint256 _tokenId,
                  uint256 _mintsAllowed
              ) external onlyOwner {
                  tokenPresaleMintsPerAddress[_tokenId] = _mintsAllowed;
              }
              /**
               * @notice Reduce the presale max supply of tokens for a given token id
               * @param _newPresaleMaxSupply The new maximum supply of tokens available to mint
               * @param _tokenId The token id to reduce the max supply for
               */
              function reducePresaleMaxSupply(
                  uint256 _tokenId,
                  uint256 _newPresaleMaxSupply
              ) external onlyOwner {
                  require(
                      tokenPresaleMaxSupply[_tokenId] == 0 ||
                          _newPresaleMaxSupply < tokenPresaleMaxSupply[_tokenId],
                      "NEW_MAX_SUPPLY_TOO_HIGH"
                  );
                  tokenPresaleMaxSupply[_tokenId] = _newPresaleMaxSupply;
              }
              /**
               * @notice Update the start time for presale mint for a given token
               */
              function setTokenPresaleStartTime(
                  uint256 _tokenId,
                  uint256 _presaleStartTime
              ) external onlyOwner {
                  require(_presaleStartTime > block.timestamp, "TIME_IN_PAST");
                  tokenPresaleSaleStartTime[_tokenId] = _presaleStartTime;
              }
              /**
               * @notice Update the end time for presale mint for a given token
               */
              function setTokenPresaleEndTime(
                  uint256 _tokenId,
                  uint256 _presaleEndTime
              ) external onlyOwner {
                  require(_presaleEndTime > block.timestamp, "TIME_IN_PAST");
                  tokenPresaleSaleEndTime[_tokenId] = _presaleEndTime;
              }
              /**
               * @notice Update whether or not to use the automatic presale times for a given token
               */
              function setTokenUsePresaleTimes(
                  uint256 _tokenId,
                  bool _usePresaleTimes
              ) external onlyOwner {
                  require(
                      tokenUsePresaleTimes[_tokenId] != _usePresaleTimes,
                      "NEW_STATE_IDENTICAL_TO_OLD_STATE"
                  );
                  tokenUsePresaleTimes[_tokenId] = _usePresaleTimes;
              }
              /**
               * @notice Returns if presale times are active for a given token
               */
              function tokenPresaleTimeIsActive(
                  uint256 _tokenId
              ) public view returns (bool) {
                  if (tokenUsePresaleTimes[_tokenId] == false) {
                      return true;
                  }
                  return
                      block.timestamp >= tokenPresaleSaleStartTime[_tokenId] &&
                      block.timestamp <= tokenPresaleSaleEndTime[_tokenId];
              }
              /**
               * @notice Verify that a signed message is validly signed by the presaleSignerAddress
               */
              function verifySignerAddress(
                  bytes32 _messageHash,
                  bytes calldata _signature
              ) private view returns (bool) {
                  return
                      presaleSignerAddress ==
                      _messageHash.toEthSignedMessageHash().recover(_signature);
              }
              /**
               * @notice Allow for allowlist minting of tokens
               */
              function presaleMint(
                  bytes32 _messageHash,
                  bytes calldata _signature,
                  uint256 _tokenId,
                  uint256 _numTokens,
                  uint256 _maximumAllowedMints
              ) external payable originalUser nonReentrant {
                  require(tokenPresaleSaleActive[_tokenId], "PRESALE_IS_NOT_ACTIVE");
                  require(
                      tokenPresaleTimeIsActive(_tokenId),
                      "PRESALE_TIME_IS_NOT_ACTIVE"
                  );
                  require(
                      !tokenMintingPermanentlyDisabled[_tokenId],
                      "MINTING_PERMANENTLY_DISABLED"
                  );
                  require(
                      tokenPresaleMintsPerAddress[_tokenId] == 0 ||
                          tokensMintedByAddress[msg.sender][_tokenId] + _numTokens <=
                          tokenPresaleMintsPerAddress[_tokenId],
                      "MAX_MINTS_PER_ADDRESS_EXCEEDED"
                  );
                  require(
                      _maximumAllowedMints == 0 ||
                          tokensMintedByAddress[msg.sender][_tokenId] + _numTokens <=
                          _maximumAllowedMints,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      tokenPresaleMaxSupply[_tokenId] == 0 ||
                          totalSupply(_tokenId) + _numTokens <=
                          tokenPresaleMaxSupply[_tokenId],
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  require(
                      msg.value == tokenPresalePrice[_tokenId] * _numTokens,
                      "PAYMENT_INCORRECT"
                  );
                  require(
                      keccak256(abi.encode(msg.sender, _maximumAllowedMints, _tokenId)) ==
                          _messageHash,
                      "MESSAGE_INVALID"
                  );
                  require(
                      verifySignerAddress(_messageHash, _signature),
                      "SIGNATURE_VALIDATION_FAILED"
                  );
                  tokensMintedByAddress[msg.sender][_tokenId] += _numTokens;
                  _mint(msg.sender, _tokenId, _numTokens, "");
                  if (
                      tokenPresaleMaxSupply[_tokenId] != 0 &&
                      totalSupply(_tokenId) >= tokenPresaleMaxSupply[_tokenId]
                  ) {
                      tokenPresaleSaleActive[_tokenId] = false;
                  }
              }
              /**
               * @notice Freeze all payout addresses and percentages so they can never be changed again
               */
              function freezePayoutAddresses() external onlyOwner {
                  require(!payoutAddressesFrozen, "PAYOUT_ADDRESSES_ALREADY_FROZEN");
                  payoutAddressesFrozen = true;
              }
              /**
               * @notice Update payout addresses and basis points for each addresses' respective share of contract funds
               */
              function updatePayoutAddressesAndBasisPoints(
                  address[] calldata _payoutAddresses,
                  uint256[] calldata _payoutBasisPoints
              ) external onlyOwner {
                  require(!payoutAddressesFrozen, "PAYOUT_ADDRESSES_FROZEN");
                  require(
                      _payoutAddresses.length == _payoutBasisPoints.length,
                      "ARRAY_LENGTHS_MUST_MATCH"
                  );
                  uint256 totalBasisPoints = 0;
                  for (uint256 i = 0; i < _payoutBasisPoints.length; i++) {
                      totalBasisPoints += _payoutBasisPoints[i];
                  }
                  require(totalBasisPoints == 10000, "TOTAL_BASIS_POINTS_MUST_BE_10000");
                  payoutAddresses = _payoutAddresses;
                  payoutBasisPoints = _payoutBasisPoints;
              }
              /**
               * @notice Withdraws all funds held within contract
               */
              function withdraw() external onlyOwner nonReentrant {
                  require(address(this).balance > 0, "CONTRACT_HAS_NO_BALANCE");
                  uint256 balance = address(this).balance;
                  for (uint256 i = 0; i < payoutAddresses.length; i++) {
                      uint256 amount = (balance * payoutBasisPoints[i]) / 10000;
                      (bool success, ) = payoutAddresses[i].call{value: amount}("");
                      require(success, "Transfer failed.");
                  }
              }
              /**
               * @notice Override default ERC-1155 setApprovalForAll to require that the operator is not from a blocklisted exchange
               * @param operator Address to add to the set of authorized operators
               * @param approved True if the operator is approved, false to revoke approval
               */
              function setApprovalForAll(
                  address operator,
                  bool approved
              ) public override {
                  super.setApprovalForAll(operator, approved);
              }
              function _beforeTokenTransfer(
                  address operator,
                  address from,
                  address to,
                  uint256[] memory ids,
                  uint256[] memory amounts,
                  bytes memory data
              ) internal override whenNotPaused {
                  super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {ERC721Enumerable, ERC721} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
          /**
           * @title Basic Enumerable ERC721 Contract
           * @author Ben Yu
           * @notice An ERC721Enumerable contract with basic functionality
           */
          contract EnumerableERC721 is ERC721Enumerable, Ownable {
              using Counters for Counters.Counter;
              Counters.Counter private supplyCounter;
              uint256 public constant PRICE = 0.01 ether;
              uint256 public constant MAX_SUPPLY = 1000;
              string public baseTokenURI =
                  "ipfs://bafybeih5lgrstt7kredzhpcvmft2qefue5pl3ykrdktadw5w62zd7cbkja/";
              bool public publicSaleActive;
              /**
               * @notice Initialize the contract
               */
              constructor() ERC721("Test Contract", "TEST") {
                  // Start token IDs at 1
                  supplyCounter.increment();
              }
              /**
               * @notice Override the default base URI function to provide a real base URI
               */
              function _baseURI() internal view virtual override returns (string memory) {
                  return baseTokenURI;
              }
              /**
               * @notice Update the base token URI
               * @param _newBaseURI New base URI
               */
              function setBaseURI(string calldata _newBaseURI) external onlyOwner {
                  baseTokenURI = _newBaseURI;
              }
              /**
               * @notice Allows for public minting of tokens
               * @param _mintNumber Number of tokens to mint
               */
              function publicMint(uint256 _mintNumber) external payable virtual {
                  require(msg.value == PRICE * _mintNumber, "INVALID_PRICE");
                  require((totalSupply() + _mintNumber) <= MAX_SUPPLY, "MINT_TOO_LARGE");
                  for (uint256 i = 0; i < _mintNumber; i++) {
                      _safeMint(msg.sender, supplyCounter.current());
                      supplyCounter.increment();
                  }
              }
              /**
               * @notice Allow owner to send `mintNumber` tokens without cost to multiple addresses
               * @param _receivers Array of addresses to send tokens to
               * @param _mintNumber Number of tokens to send to each address
               */
              function gift(
                  address[] calldata _receivers,
                  uint256 _mintNumber
              ) external onlyOwner {
                  require(
                      (totalSupply() + (_receivers.length * _mintNumber)) <= MAX_SUPPLY,
                      "MINT_TOO_LARGE"
                  );
                  for (uint256 i = 0; i < _receivers.length; i++) {
                      for (uint256 j = 0; j < _mintNumber; j++) {
                          _safeMint(_receivers[i], supplyCounter.current());
                          supplyCounter.increment();
                      }
                  }
              }
              /**
               * @notice Allow contract owner to withdraw funds
               */
              function withdraw() external onlyOwner {
                  payable(owner()).transfer(address(this).balance);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev This is a base contract to aid in writing upgradeable diamond facet 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.
           *
           * 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.
           */
          import {ERC721A__InitializableStorage} from './ERC721A__InitializableStorage.sol';
          abstract contract ERC721A__Initializable {
              using ERC721A__InitializableStorage for ERC721A__InitializableStorage.Layout;
              /**
               * @dev Modifier to protect an initializer function from being invoked twice.
               */
              modifier initializerERC721A() {
                  // If the contract is initializing we ignore whether _initialized is set in order to support multiple
                  // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
                  // contract may have been reentered.
                  require(
                      ERC721A__InitializableStorage.layout()._initializing
                          ? _isConstructor()
                          : !ERC721A__InitializableStorage.layout()._initialized,
                      'ERC721A__Initializable: contract is already initialized'
                  );
                  bool isTopLevelCall = !ERC721A__InitializableStorage.layout()._initializing;
                  if (isTopLevelCall) {
                      ERC721A__InitializableStorage.layout()._initializing = true;
                      ERC721A__InitializableStorage.layout()._initialized = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      ERC721A__InitializableStorage.layout()._initializing = false;
                  }
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} modifier, directly or indirectly.
               */
              modifier onlyInitializingERC721A() {
                  require(
                      ERC721A__InitializableStorage.layout()._initializing,
                      'ERC721A__Initializable: contract is not initializing'
                  );
                  _;
              }
              /// @dev Returns true if and only if the function is running in the constructor
              function _isConstructor() private view returns (bool) {
                  // extcodesize checks the size of the code stored in an address, and
                  // address returns the current address. Since the code is still not
                  // deployed when running a constructor, any checks on its code size will
                  // yield zero, making it an effective way to detect if a contract is
                  // under construction or not.
                  address self = address(this);
                  uint256 cs;
                  assembly {
                      cs := extcodesize(self)
                  }
                  return cs == 0;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev This is a base storage for the  initialization function for upgradeable diamond facet contracts
           **/
          library ERC721A__InitializableStorage {
              struct Layout {
                  /*
                   * Indicates that the contract has been initialized.
                   */
                  bool _initialized;
                  /*
                   * Indicates that the contract is in the process of being initialized.
                   */
                  bool _initializing;
              }
              bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.initializable.facet');
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          library ERC721AStorage {
              // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
              struct TokenApprovalRef {
                  address value;
              }
              struct Layout {
                  // =============================================================
                  //                            STORAGE
                  // =============================================================
                  // The next token ID to be minted.
                  uint256 _currentIndex;
                  // The number of tokens burned.
                  uint256 _burnCounter;
                  // Token name
                  string _name;
                  // Token symbol
                  string _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) _packedOwnerships;
                  // Mapping owner address to address data.
                  //
                  // Bits Layout:
                  // - [0..63]    `balance`
                  // - [64..127]  `numberMinted`
                  // - [128..191] `numberBurned`
                  // - [192..255] `aux`
                  mapping(address => uint256) _packedAddressData;
                  // Mapping from token ID to approved address.
                  mapping(uint256 => ERC721AStorage.TokenApprovalRef) _tokenApprovals;
                  // Mapping from owner to operator approvals
                  mapping(address => mapping(address => bool)) _operatorApprovals;
              }
              bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC721A');
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721AUpgradeable.sol';
          import {ERC721AStorage} from './ERC721AStorage.sol';
          import './ERC721A__Initializable.sol';
          /**
           * @dev Interface of ERC721 token receiver.
           */
          interface ERC721A__IERC721ReceiverUpgradeable {
              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 ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
              using ERC721AStorage for ERC721AStorage.Layout;
              // =============================================================
              //                           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;
              // =============================================================
              //                          CONSTRUCTOR
              // =============================================================
              function __ERC721A_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
                  __ERC721A_init_unchained(name_, symbol_);
              }
              function __ERC721A_init_unchained(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
                  ERC721AStorage.layout()._name = name_;
                  ERC721AStorage.layout()._symbol = symbol_;
                  ERC721AStorage.layout()._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 ERC721AStorage.layout()._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 ERC721AStorage.layout()._currentIndex - ERC721AStorage.layout()._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 ERC721AStorage.layout()._currentIndex - _startTokenId();
                  }
              }
              /**
               * @dev Returns the total number of tokens burned.
               */
              function _totalBurned() internal view virtual returns (uint256) {
                  return ERC721AStorage.layout()._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 ERC721AStorage.layout()._packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
              }
              /**
               * Returns the number of tokens minted by `owner`.
               */
              function _numberMinted(address owner) internal view returns (uint256) {
                  return
                      (ERC721AStorage.layout()._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
                      (ERC721AStorage.layout()._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(ERC721AStorage.layout()._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 = ERC721AStorage.layout()._packedAddressData[owner];
                  uint256 auxCasted;
                  // Cast `aux` with assembly to avoid redundant masking.
                  assembly {
                      auxCasted := aux
                  }
                  packed = (packed & _BITMASK_AUX_COMPLEMENT) | (auxCasted << _BITPOS_AUX);
                  ERC721AStorage.layout()._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 ERC721AStorage.layout()._name;
              }
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() public view virtual override returns (string memory) {
                  return ERC721AStorage.layout()._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(ERC721AStorage.layout()._packedOwnerships[index]);
              }
              /**
               * @dev Initializes the ownership slot minted at `index` for efficiency purposes.
               */
              function _initializeOwnershipAt(uint256 index) internal virtual {
                  if (ERC721AStorage.layout()._packedOwnerships[index] == 0) {
                      ERC721AStorage.layout()._packedOwnerships[index] = _packedOwnershipOf(index);
                  }
              }
              /**
               * Returns the packed ownership data of `tokenId`.
               */
              function _packedOwnershipOf(uint256 tokenId) private view returns (uint256 packed) {
                  if (_startTokenId() <= tokenId) {
                      packed = ERC721AStorage.layout()._packedOwnerships[tokenId];
                      // If not burned.
                      if (packed & _BITMASK_BURNED == 0) {
                          // If the data at the starting slot does not exist, start the scan.
                          if (packed == 0) {
                              if (tokenId >= ERC721AStorage.layout()._currentIndex) revert OwnerQueryForNonexistentToken();
                              // 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, `tokenId` will not underflow.
                              //
                              // We can directly compare the packed value.
                              // If the address is zero, packed will be zero.
                              for (;;) {
                                  unchecked {
                                      packed = ERC721AStorage.layout()._packedOwnerships[--tokenId];
                                  }
                                  if (packed == 0) continue;
                                  return packed;
                              }
                          }
                          // Otherwise, the data exists and is not burned. We can skip the scan.
                          // This is possible because we have already achieved the target condition.
                          // This saves 2143 gas on transfers of initialized tokens.
                          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. See {ERC721A-_approve}.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               */
              function approve(address to, uint256 tokenId) public payable virtual override {
                  _approve(to, tokenId, true);
              }
              /**
               * @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 ERC721AStorage.layout()._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 {
                  ERC721AStorage.layout()._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 ERC721AStorage.layout()._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 < ERC721AStorage.layout()._currentIndex && // If within bounds,
                      ERC721AStorage.layout()._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)
              {
                  ERC721AStorage.TokenApprovalRef storage tokenApproval = ERC721AStorage.layout()._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 payable 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.
                      --ERC721AStorage.layout()._packedAddressData[from]; // Updates: `balance -= 1`.
                      ++ERC721AStorage.layout()._packedAddressData[to]; // Updates: `balance += 1`.
                      // Updates:
                      // - `address` to the next owner.
                      // - `startTimestamp` to the timestamp of transfering.
                      // - `burned` to `false`.
                      // - `nextInitialized` to `true`.
                      ERC721AStorage.layout()._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 (ERC721AStorage.layout()._packedOwnerships[nextTokenId] == 0) {
                              // If the next slot is within bounds.
                              if (nextTokenId != ERC721AStorage.layout()._currentIndex) {
                                  // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                                  ERC721AStorage.layout()._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 payable 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 payable 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__IERC721ReceiverUpgradeable(to).onERC721Received(_msgSenderERC721A(), from, tokenId, _data)
                  returns (bytes4 retval) {
                      return retval == ERC721A__IERC721ReceiverUpgradeable(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 = ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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();
                      ERC721AStorage.layout()._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 = ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._packedOwnerships[startTokenId] = _packOwnershipData(
                          to,
                          _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
                      );
                      emit ConsecutiveTransfer(startTokenId, startTokenId + quantity - 1, address(0), to);
                      ERC721AStorage.layout()._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 = ERC721AStorage.layout()._currentIndex;
                          uint256 index = end - quantity;
                          do {
                              if (!_checkContractOnERC721Received(address(0), to, index++, _data)) {
                                  revert TransferToNonERC721ReceiverImplementer();
                              }
                          } while (index < end);
                          // Reentrancy protection.
                          if (ERC721AStorage.layout()._currentIndex != end) revert();
                      }
                  }
              }
              /**
               * @dev Equivalent to `_safeMint(to, quantity, '')`.
               */
              function _safeMint(address to, uint256 quantity) internal virtual {
                  _safeMint(to, quantity, '');
              }
              // =============================================================
              //                       APPROVAL OPERATIONS
              // =============================================================
              /**
               * @dev Equivalent to `_approve(to, tokenId, false)`.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _approve(to, tokenId, false);
              }
              /**
               * @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:
               *
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function _approve(
                  address to,
                  uint256 tokenId,
                  bool approvalCheck
              ) internal virtual {
                  address owner = ownerOf(tokenId);
                  if (approvalCheck)
                      if (_msgSenderERC721A() != owner)
                          if (!isApprovedForAll(owner, _msgSenderERC721A())) {
                              revert ApprovalCallerNotOwnerNorApproved();
                          }
                  ERC721AStorage.layout()._tokenApprovals[tokenId].value = to;
                  emit Approval(owner, to, tokenId);
              }
              // =============================================================
              //                        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;`.
                      ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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 (ERC721AStorage.layout()._packedOwnerships[nextTokenId] == 0) {
                              // If the next slot is within bounds.
                              if (nextTokenId != ERC721AStorage.layout()._currentIndex) {
                                  // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                                  ERC721AStorage.layout()._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 {
                      ERC721AStorage.layout()._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 = ERC721AStorage.layout()._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);
                  ERC721AStorage.layout()._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
          pragma solidity ^0.8.0;
          import {ERC4907AUpgradeable} from './ERC4907AUpgradeable.sol';
          library ERC4907AStorage {
              struct Layout {
                  // Mapping from token ID to user info.
                  //
                  // Bits Layout:
                  // - [0..159]   `user`
                  // - [160..223] `expires`
                  mapping(uint256 => uint256) _packedUserInfo;
              }
              bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC4907A');
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC4907AUpgradeable.sol';
          import '../ERC721AUpgradeable.sol';
          import {ERC4907AStorage} from './ERC4907AStorage.sol';
          import '../ERC721A__Initializable.sol';
          /**
           * @title ERC4907A
           *
           * @dev [ERC4907](https://eips.ethereum.org/EIPS/eip-4907) compliant
           * extension of ERC721A, which allows owners and authorized addresses
           * to add a time-limited role with restricted permissions to ERC721 tokens.
           */
          abstract contract ERC4907AUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, IERC4907AUpgradeable {
              using ERC4907AStorage for ERC4907AStorage.Layout;
              function __ERC4907A_init() internal onlyInitializingERC721A {
                  __ERC4907A_init_unchained();
              }
              function __ERC4907A_init_unchained() internal onlyInitializingERC721A {}
              // The bit position of `expires` in packed user info.
              uint256 private constant _BITPOS_EXPIRES = 160;
              /**
               * @dev Sets the `user` and `expires` for `tokenId`.
               * The zero address indicates there is no user.
               *
               * Requirements:
               *
               * - The caller must own `tokenId` or be an approved operator.
               */
              function setUser(
                  uint256 tokenId,
                  address user,
                  uint64 expires
              ) public virtual override {
                  // Require the caller to be either the token owner or an approved operator.
                  address owner = ownerOf(tokenId);
                  if (_msgSenderERC721A() != owner)
                      if (!isApprovedForAll(owner, _msgSenderERC721A()))
                          if (getApproved(tokenId) != _msgSenderERC721A()) revert SetUserCallerNotOwnerNorApproved();
                  ERC4907AStorage.layout()._packedUserInfo[tokenId] =
                      (uint256(expires) << _BITPOS_EXPIRES) |
                      uint256(uint160(user));
                  emit UpdateUser(tokenId, user, expires);
              }
              /**
               * @dev Returns the user address for `tokenId`.
               * The zero address indicates that there is no user or if the user is expired.
               */
              function userOf(uint256 tokenId) public view virtual override returns (address) {
                  uint256 packed = ERC4907AStorage.layout()._packedUserInfo[tokenId];
                  assembly {
                      // Branchless `packed *= (block.timestamp <= expires ? 1 : 0)`.
                      // If the `block.timestamp == expires`, the `lt` clause will be true
                      // if there is a non-zero user address in the lower 160 bits of `packed`.
                      packed := mul(
                          packed,
                          // `block.timestamp <= expires ? 1 : 0`.
                          lt(shl(_BITPOS_EXPIRES, timestamp()), packed)
                      )
                  }
                  return address(uint160(packed));
              }
              /**
               * @dev Returns the user's expires of `tokenId`.
               */
              function userExpires(uint256 tokenId) public view virtual override returns (uint256) {
                  return ERC4907AStorage.layout()._packedUserInfo[tokenId] >> _BITPOS_EXPIRES;
              }
              /**
               * @dev Override of {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  returns (bool)
              {
                  // The interface ID for ERC4907 is `0xad092b5c`,
                  // as defined in [ERC4907](https://eips.ethereum.org/EIPS/eip-4907).
                  return super.supportsInterface(interfaceId) || interfaceId == 0xad092b5c;
              }
              /**
               * @dev Returns the user address for `tokenId`, ignoring the expiry status.
               */
              function _explicitUserOf(uint256 tokenId) internal view virtual returns (address) {
                  return address(uint160(ERC4907AStorage.layout()._packedUserInfo[tokenId]));
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721AQueryableUpgradeable.sol';
          import '../ERC721AUpgradeable.sol';
          import '../ERC721A__Initializable.sol';
          /**
           * @title ERC721AQueryable.
           *
           * @dev ERC721A subclass with convenience query functions.
           */
          abstract contract ERC721AQueryableUpgradeable is
              ERC721A__Initializable,
              ERC721AUpgradeable,
              IERC721AQueryableUpgradeable
          {
              function __ERC721AQueryable_init() internal onlyInitializingERC721A {
                  __ERC721AQueryable_init_unchained();
              }
              function __ERC721AQueryable_init_unchained() internal onlyInitializingERC721A {}
              /**
               * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
               *
               * If the `tokenId` is out of bounds:
               *
               * - `addr = address(0)`
               * - `startTimestamp = 0`
               * - `burned = false`
               * - `extraData = 0`
               *
               * If the `tokenId` is burned:
               *
               * - `addr = <Address of owner before token was burned>`
               * - `startTimestamp = <Timestamp when token was burned>`
               * - `burned = true`
               * - `extraData = <Extra data when token was burned>`
               *
               * Otherwise:
               *
               * - `addr = <Address of owner>`
               * - `startTimestamp = <Timestamp of start of ownership>`
               * - `burned = false`
               * - `extraData = <Extra data at start of ownership>`
               */
              function explicitOwnershipOf(uint256 tokenId) public view virtual override returns (TokenOwnership memory) {
                  TokenOwnership memory ownership;
                  if (tokenId < _startTokenId() || tokenId >= _nextTokenId()) {
                      return ownership;
                  }
                  ownership = _ownershipAt(tokenId);
                  if (ownership.burned) {
                      return ownership;
                  }
                  return _ownershipOf(tokenId);
              }
              /**
               * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
               * See {ERC721AQueryable-explicitOwnershipOf}
               */
              function explicitOwnershipsOf(uint256[] calldata tokenIds)
                  external
                  view
                  virtual
                  override
                  returns (TokenOwnership[] memory)
              {
                  unchecked {
                      uint256 tokenIdsLength = tokenIds.length;
                      TokenOwnership[] memory ownerships = new TokenOwnership[](tokenIdsLength);
                      for (uint256 i; i != tokenIdsLength; ++i) {
                          ownerships[i] = explicitOwnershipOf(tokenIds[i]);
                      }
                      return ownerships;
                  }
              }
              /**
               * @dev Returns an array of token IDs owned by `owner`,
               * in the range [`start`, `stop`)
               * (i.e. `start <= tokenId < stop`).
               *
               * This function allows for tokens to be queried if the collection
               * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
               *
               * Requirements:
               *
               * - `start < stop`
               */
              function tokensOfOwnerIn(
                  address owner,
                  uint256 start,
                  uint256 stop
              ) external view virtual override returns (uint256[] memory) {
                  unchecked {
                      if (start >= stop) revert InvalidQueryRange();
                      uint256 tokenIdsIdx;
                      uint256 stopLimit = _nextTokenId();
                      // Set `start = max(start, _startTokenId())`.
                      if (start < _startTokenId()) {
                          start = _startTokenId();
                      }
                      // Set `stop = min(stop, stopLimit)`.
                      if (stop > stopLimit) {
                          stop = stopLimit;
                      }
                      uint256 tokenIdsMaxLength = balanceOf(owner);
                      // Set `tokenIdsMaxLength = min(balanceOf(owner), stop - start)`,
                      // to cater for cases where `balanceOf(owner)` is too big.
                      if (start < stop) {
                          uint256 rangeLength = stop - start;
                          if (rangeLength < tokenIdsMaxLength) {
                              tokenIdsMaxLength = rangeLength;
                          }
                      } else {
                          tokenIdsMaxLength = 0;
                      }
                      uint256[] memory tokenIds = new uint256[](tokenIdsMaxLength);
                      if (tokenIdsMaxLength == 0) {
                          return tokenIds;
                      }
                      // We need to call `explicitOwnershipOf(start)`,
                      // because the slot at `start` may not be initialized.
                      TokenOwnership memory ownership = explicitOwnershipOf(start);
                      address currOwnershipAddr;
                      // If the starting slot exists (i.e. not burned), initialize `currOwnershipAddr`.
                      // `ownership.address` will not be zero, as `start` is clamped to the valid token ID range.
                      if (!ownership.burned) {
                          currOwnershipAddr = ownership.addr;
                      }
                      for (uint256 i = start; i != stop && tokenIdsIdx != tokenIdsMaxLength; ++i) {
                          ownership = _ownershipAt(i);
                          if (ownership.burned) {
                              continue;
                          }
                          if (ownership.addr != address(0)) {
                              currOwnershipAddr = ownership.addr;
                          }
                          if (currOwnershipAddr == owner) {
                              tokenIds[tokenIdsIdx++] = i;
                          }
                      }
                      // Downsize the array to fit.
                      assembly {
                          mstore(tokenIds, tokenIdsIdx)
                      }
                      return tokenIds;
                  }
              }
              /**
               * @dev Returns an array of token IDs owned by `owner`.
               *
               * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
               * It is meant to be called off-chain.
               *
               * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
               * multiple smaller scans if the collection is large enough to cause
               * an out-of-gas error (10K collections should be fine).
               */
              function tokensOfOwner(address owner) external view virtual override returns (uint256[] memory) {
                  unchecked {
                      uint256 tokenIdsIdx;
                      address currOwnershipAddr;
                      uint256 tokenIdsLength = balanceOf(owner);
                      uint256[] memory tokenIds = new uint256[](tokenIdsLength);
                      TokenOwnership memory ownership;
                      for (uint256 i = _startTokenId(); tokenIdsIdx != tokenIdsLength; ++i) {
                          ownership = _ownershipAt(i);
                          if (ownership.burned) {
                              continue;
                          }
                          if (ownership.addr != address(0)) {
                              currOwnershipAddr = ownership.addr;
                          }
                          if (currOwnershipAddr == owner) {
                              tokenIds[tokenIdsIdx++] = i;
                          }
                      }
                      return tokenIds;
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import '../IERC721AUpgradeable.sol';
          /**
           * @dev Interface of ERC4907A.
           */
          interface IERC4907AUpgradeable is IERC721AUpgradeable {
              /**
               * The caller must own the token or be an approved operator.
               */
              error SetUserCallerNotOwnerNorApproved();
              /**
               * @dev Emitted when the `user` of an NFT or the `expires` of the `user` is changed.
               * The zero address for user indicates that there is no user address.
               */
              event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);
              /**
               * @dev Sets the `user` and `expires` for `tokenId`.
               * The zero address indicates there is no user.
               *
               * Requirements:
               *
               * - The caller must own `tokenId` or be an approved operator.
               */
              function setUser(
                  uint256 tokenId,
                  address user,
                  uint64 expires
              ) external;
              /**
               * @dev Returns the user address for `tokenId`.
               * The zero address indicates that there is no user or if the user is expired.
               */
              function userOf(uint256 tokenId) external view returns (address);
              /**
               * @dev Returns the user's expires of `tokenId`.
               */
              function userExpires(uint256 tokenId) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import '../IERC721AUpgradeable.sol';
          /**
           * @dev Interface of ERC721AQueryable.
           */
          interface IERC721AQueryableUpgradeable is IERC721AUpgradeable {
              /**
               * Invalid query range (`start` >= `stop`).
               */
              error InvalidQueryRange();
              /**
               * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
               *
               * If the `tokenId` is out of bounds:
               *
               * - `addr = address(0)`
               * - `startTimestamp = 0`
               * - `burned = false`
               * - `extraData = 0`
               *
               * If the `tokenId` is burned:
               *
               * - `addr = <Address of owner before token was burned>`
               * - `startTimestamp = <Timestamp when token was burned>`
               * - `burned = true`
               * - `extraData = <Extra data when token was burned>`
               *
               * Otherwise:
               *
               * - `addr = <Address of owner>`
               * - `startTimestamp = <Timestamp of start of ownership>`
               * - `burned = false`
               * - `extraData = <Extra data at start of ownership>`
               */
              function explicitOwnershipOf(uint256 tokenId) external view returns (TokenOwnership memory);
              /**
               * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
               * See {ERC721AQueryable-explicitOwnershipOf}
               */
              function explicitOwnershipsOf(uint256[] memory tokenIds) external view returns (TokenOwnership[] memory);
              /**
               * @dev Returns an array of token IDs owned by `owner`,
               * in the range [`start`, `stop`)
               * (i.e. `start <= tokenId < stop`).
               *
               * This function allows for tokens to be queried if the collection
               * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
               *
               * Requirements:
               *
               * - `start < stop`
               */
              function tokensOfOwnerIn(
                  address owner,
                  uint256 start,
                  uint256 stop
              ) external view returns (uint256[] memory);
              /**
               * @dev Returns an array of token IDs owned by `owner`.
               *
               * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
               * It is meant to be called off-chain.
               *
               * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
               * multiple smaller scans if the collection is large enough to cause
               * an out-of-gas error (10K collections should be fine).
               */
              function tokensOfOwner(address owner) external view returns (uint256[] memory);
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          /**
           * @dev Interface of ERC721A.
           */
          interface IERC721AUpgradeable {
              /**
               * 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 payable;
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @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 payable;
              /**
               * @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 payable;
              /**
               * @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
          pragma solidity ^0.8.13;
          interface IOperatorFilterRegistry {
              /**
               * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
               *         true if supplied registrant address is not registered.
               */
              function isOperatorAllowed(address registrant, address operator) external view returns (bool);
              /**
               * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
               */
              function register(address registrant) external;
              /**
               * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
               */
              function registerAndSubscribe(address registrant, address subscription) external;
              /**
               * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
               *         address without subscribing.
               */
              function registerAndCopyEntries(address registrant, address registrantToCopy) external;
              /**
               * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
               *         Note that this does not remove any filtered addresses or codeHashes.
               *         Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
               */
              function unregister(address addr) external;
              /**
               * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
               */
              function updateOperator(address registrant, address operator, bool filtered) external;
              /**
               * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
               */
              function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
              /**
               * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
               */
              function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
              /**
               * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
               */
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
              /**
               * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
               *         subscription if present.
               *         Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
               *         subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
               *         used.
               */
              function subscribe(address registrant, address registrantToSubscribe) external;
              /**
               * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
               */
              function unsubscribe(address registrant, bool copyExistingEntries) external;
              /**
               * @notice Get the subscription address of a given registrant, if any.
               */
              function subscriptionOf(address addr) external returns (address registrant);
              /**
               * @notice Get the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscribers(address registrant) external returns (address[] memory);
              /**
               * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscriberAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
               */
              function copyEntriesOf(address registrant, address registrantToCopy) external;
              /**
               * @notice Returns true if operator is filtered by a given address or its subscription.
               */
              function isOperatorFiltered(address registrant, address operator) external returns (bool);
              /**
               * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
               */
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
              /**
               * @notice Returns true if a codeHash is filtered by a given address or its subscription.
               */
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
              /**
               * @notice Returns a list of filtered operators for a given address or its subscription.
               */
              function filteredOperators(address addr) external returns (address[] memory);
              /**
               * @notice Returns the set of filtered codeHashes for a given address or its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashes(address addr) external returns (bytes32[] memory);
              /**
               * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredOperatorAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
              /**
               * @notice Returns true if an address has registered
               */
              function isRegistered(address addr) external returns (bool);
              /**
               * @dev Convenience method to compute the code hash of an arbitrary contract
               */
              function codeHashOf(address addr) external returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {IOperatorFilterRegistry} from "../IOperatorFilterRegistry.sol";
          import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          /**
           * @title  OperatorFiltererUpgradeable
           * @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
           *         registrant's entries in the OperatorFilterRegistry when the init function is called.
           * @dev    This smart contract is meant to be inherited by token contracts so they can use the following:
           *         - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
           *         - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
           */
          abstract contract OperatorFiltererUpgradeable is Initializable {
              /// @notice Emitted when an operator is not allowed.
              error OperatorNotAllowed(address operator);
              IOperatorFilterRegistry constant OPERATOR_FILTER_REGISTRY =
                  IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E);
              /// @dev The upgradeable initialize function that should be called when the contract is being upgraded.
              function __OperatorFilterer_init(address subscriptionOrRegistrantToCopy, bool subscribe)
                  internal
                  onlyInitializing
              {
                  // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
                  // will not revert, but the contract will need to be registered with the registry once it is deployed in
                  // order for the modifier to filter addresses.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      if (!OPERATOR_FILTER_REGISTRY.isRegistered(address(this))) {
                          if (subscribe) {
                              OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
                          } else {
                              if (subscriptionOrRegistrantToCopy != address(0)) {
                                  OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
                              } else {
                                  OPERATOR_FILTER_REGISTRY.register(address(this));
                              }
                          }
                      }
                  }
              }
              /**
               * @dev A helper modifier to check if the operator is allowed.
               */
              modifier onlyAllowedOperator(address from) virtual {
                  // Allow spending tokens from addresses with balance
                  // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
                  // from an EOA.
                  if (from != msg.sender) {
                      _checkFilterOperator(msg.sender);
                  }
                  _;
              }
              /**
               * @dev A helper modifier to check if the operator approval is allowed.
               */
              modifier onlyAllowedOperatorApproval(address operator) virtual {
                  _checkFilterOperator(operator);
                  _;
              }
              /**
               * @dev A helper function to check if the operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting or
                      // upgraded contracts may specify their own OperatorFilterRegistry implementations, which may behave
                      // differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {OperatorFiltererUpgradeable} from "./OperatorFiltererUpgradeable.sol";
          /**
           * @title  Upgradeable storage layout for RevokableOperatorFiltererUpgradeable.
           * @notice Upgradeable contracts must use a storage layout that can be used across upgrades.
           *         Only append new variables to the end of the layout.
           */
          library RevokableOperatorFiltererUpgradeableStorage {
              struct Layout {
                  /// @dev Whether the OperatorFilterRegistry has been revoked.
                  bool _isOperatorFilterRegistryRevoked;
              }
              /// @dev The storage slot for the layout.
              bytes32 internal constant STORAGE_SLOT = keccak256("RevokableOperatorFiltererUpgradeable.contracts.storage");
              /// @dev The layout of the storage.
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          /**
           * @title  RevokableOperatorFilterer
           * @notice This contract is meant to allow contracts to permanently opt out of the OperatorFilterRegistry. The Registry
           *         itself has an "unregister" function, but if the contract is ownable, the owner can re-register at any point.
           *         As implemented, this abstract contract allows the contract owner to toggle the
           *         isOperatorFilterRegistryRevoked flag in order to permanently bypass the OperatorFilterRegistry checks.
           */
          abstract contract RevokableOperatorFiltererUpgradeable is OperatorFiltererUpgradeable {
              using RevokableOperatorFiltererUpgradeableStorage for RevokableOperatorFiltererUpgradeableStorage.Layout;
              error OnlyOwner();
              error AlreadyRevoked();
              event OperatorFilterRegistryRevoked();
              function __RevokableOperatorFilterer_init(address subscriptionOrRegistrantToCopy, bool subscribe) internal {
                  OperatorFiltererUpgradeable.__OperatorFilterer_init(subscriptionOrRegistrantToCopy, subscribe);
              }
              /**
               * @dev A helper function to check if the operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual override {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (
                      !RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked
                          && address(OPERATOR_FILTER_REGISTRY).code.length > 0
                  ) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting or
                      // upgraded contracts may specify their own OperatorFilterRegistry implementations, which may behave
                      // differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
              /**
               * @notice Disable the isOperatorFilterRegistryRevoked flag. OnlyOwner.
               */
              function revokeOperatorFilterRegistry() external {
                  if (msg.sender != owner()) {
                      revert OnlyOwner();
                  }
                  if (RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked) {
                      revert AlreadyRevoked();
                  }
                  RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked = true;
                  emit OperatorFilterRegistryRevoked();
              }
              function isOperatorFilterRegistryRevoked() public view returns (bool) {
                  return RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked;
              }
              /**
               * @dev assume the contract has an owner, but leave specific Ownable implementation up to inheriting contract
               */
              function owner() public view virtual returns (address);
          }
          

          File 3 of 6: Conduit
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          import { ConduitInterface } from "../interfaces/ConduitInterface.sol";
          import { ConduitItemType } from "./lib/ConduitEnums.sol";
          import { TokenTransferrer } from "../lib/TokenTransferrer.sol";
          // prettier-ignore
          import {
              ConduitTransfer,
              ConduitBatch1155Transfer
          } from "./lib/ConduitStructs.sol";
          import "./lib/ConduitConstants.sol";
          /**
           * @title Conduit
           * @author 0age
           * @notice This contract serves as an originator for "proxied" transfers. Each
           *         conduit is deployed and controlled by a "conduit controller" that can
           *         add and remove "channels" or contracts that can instruct the conduit
           *         to transfer approved ERC20/721/1155 tokens. *IMPORTANT NOTE: each
           *         conduit has an owner that can arbitrarily add or remove channels, and
           *         a malicious or negligent owner can add a channel that allows for any
           *         approved ERC20/721/1155 tokens to be taken immediately — be extremely
           *         cautious with what conduits you give token approvals to!*
           */
          contract Conduit is ConduitInterface, TokenTransferrer {
              // Set deployer as an immutable controller that can update channel statuses.
              address private immutable _controller;
              // Track the status of each channel.
              mapping(address => bool) private _channels;
              /**
               * @notice Ensure that the caller is currently registered as an open channel
               *         on the conduit.
               */
              modifier onlyOpenChannel() {
                  // Utilize assembly to access channel storage mapping directly.
                  assembly {
                      // Write the caller to scratch space.
                      mstore(ChannelKey_channel_ptr, caller())
                      // Write the storage slot for _channels to scratch space.
                      mstore(ChannelKey_slot_ptr, _channels.slot)
                      // Derive the position in storage of _channels[msg.sender]
                      // and check if the stored value is zero.
                      if iszero(
                          sload(keccak256(ChannelKey_channel_ptr, ChannelKey_length))
                      ) {
                          // The caller is not an open channel; revert with
                          // ChannelClosed(caller). First, set error signature in memory.
                          mstore(ChannelClosed_error_ptr, ChannelClosed_error_signature)
                          // Next, set the caller as the argument.
                          mstore(ChannelClosed_channel_ptr, caller())
                          // Finally, revert, returning full custom error with argument.
                          revert(ChannelClosed_error_ptr, ChannelClosed_error_length)
                      }
                  }
                  // Continue with function execution.
                  _;
              }
              /**
               * @notice In the constructor, set the deployer as the controller.
               */
              constructor() {
                  // Set the deployer as the controller.
                  _controller = msg.sender;
              }
              /**
               * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller
               *         with an open channel can call this function. Note that channels
               *         are expected to implement reentrancy protection if desired, and
               *         that cross-channel reentrancy may be possible if the conduit has
               *         multiple open channels at once. Also note that channels are
               *         expected to implement checks against transferring any zero-amount
               *         items if that constraint is desired.
               *
               * @param transfers The ERC20/721/1155 transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function execute(ConduitTransfer[] calldata transfers)
                  external
                  override
                  onlyOpenChannel
                  returns (bytes4 magicValue)
              {
                  // Retrieve the total number of transfers and place on the stack.
                  uint256 totalStandardTransfers = transfers.length;
                  // Iterate over each transfer.
                  for (uint256 i = 0; i < totalStandardTransfers; ) {
                      // Retrieve the transfer in question and perform the transfer.
                      _transfer(transfers[i]);
                      // Skip overflow check as for loop is indexed starting at zero.
                      unchecked {
                          ++i;
                      }
                  }
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.execute.selector;
              }
              /**
               * @notice Execute a sequence of batch 1155 item transfers. Only a caller
               *         with an open channel can call this function. Note that channels
               *         are expected to implement reentrancy protection if desired, and
               *         that cross-channel reentrancy may be possible if the conduit has
               *         multiple open channels at once. Also note that channels are
               *         expected to implement checks against transferring any zero-amount
               *         items if that constraint is desired.
               *
               * @param batchTransfers The 1155 batch item transfers to perform.
               *
               * @return magicValue A magic value indicating that the item transfers were
               *                    performed successfully.
               */
              function executeBatch1155(
                  ConduitBatch1155Transfer[] calldata batchTransfers
              ) external override onlyOpenChannel returns (bytes4 magicValue) {
                  // Perform 1155 batch transfers. Note that memory should be considered
                  // entirely corrupted from this point forward.
                  _performERC1155BatchTransfers(batchTransfers);
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.executeBatch1155.selector;
              }
              /**
               * @notice Execute a sequence of transfers, both single ERC20/721/1155 item
               *         transfers as well as batch 1155 item transfers. Only a caller
               *         with an open channel can call this function. Note that channels
               *         are expected to implement reentrancy protection if desired, and
               *         that cross-channel reentrancy may be possible if the conduit has
               *         multiple open channels at once. Also note that channels are
               *         expected to implement checks against transferring any zero-amount
               *         items if that constraint is desired.
               *
               * @param standardTransfers The ERC20/721/1155 item transfers to perform.
               * @param batchTransfers    The 1155 batch item transfers to perform.
               *
               * @return magicValue A magic value indicating that the item transfers were
               *                    performed successfully.
               */
              function executeWithBatch1155(
                  ConduitTransfer[] calldata standardTransfers,
                  ConduitBatch1155Transfer[] calldata batchTransfers
              ) external override onlyOpenChannel returns (bytes4 magicValue) {
                  // Retrieve the total number of transfers and place on the stack.
                  uint256 totalStandardTransfers = standardTransfers.length;
                  // Iterate over each standard transfer.
                  for (uint256 i = 0; i < totalStandardTransfers; ) {
                      // Retrieve the transfer in question and perform the transfer.
                      _transfer(standardTransfers[i]);
                      // Skip overflow check as for loop is indexed starting at zero.
                      unchecked {
                          ++i;
                      }
                  }
                  // Perform 1155 batch transfers. Note that memory should be considered
                  // entirely corrupted from this point forward aside from the free memory
                  // pointer having the default value.
                  _performERC1155BatchTransfers(batchTransfers);
                  // Return a magic value indicating that the transfers were performed.
                  magicValue = this.executeWithBatch1155.selector;
              }
              /**
               * @notice Open or close a given channel. Only callable by the controller.
               *
               * @param channel The channel to open or close.
               * @param isOpen  The status of the channel (either open or closed).
               */
              function updateChannel(address channel, bool isOpen) external override {
                  // Ensure that the caller is the controller of this contract.
                  if (msg.sender != _controller) {
                      revert InvalidController();
                  }
                  // Ensure that the channel does not already have the indicated status.
                  if (_channels[channel] == isOpen) {
                      revert ChannelStatusAlreadySet(channel, isOpen);
                  }
                  // Update the status of the channel.
                  _channels[channel] = isOpen;
                  // Emit a corresponding event.
                  emit ChannelUpdated(channel, isOpen);
              }
              /**
               * @dev Internal function to transfer a given ERC20/721/1155 item. Note that
               *      channels are expected to implement checks against transferring any
               *      zero-amount items if that constraint is desired.
               *
               * @param item The ERC20/721/1155 item to transfer.
               */
              function _transfer(ConduitTransfer calldata item) internal {
                  // Determine the transfer method based on the respective item type.
                  if (item.itemType == ConduitItemType.ERC20) {
                      // Transfer ERC20 token. Note that item.identifier is ignored and
                      // therefore ERC20 transfer items are potentially malleable — this
                      // check should be performed by the calling channel if a constraint
                      // on item malleability is desired.
                      _performERC20Transfer(item.token, item.from, item.to, item.amount);
                  } else if (item.itemType == ConduitItemType.ERC721) {
                      // Ensure that exactly one 721 item is being transferred.
                      if (item.amount != 1) {
                          revert InvalidERC721TransferAmount();
                      }
                      // Transfer ERC721 token.
                      _performERC721Transfer(
                          item.token,
                          item.from,
                          item.to,
                          item.identifier
                      );
                  } else if (item.itemType == ConduitItemType.ERC1155) {
                      // Transfer ERC1155 token.
                      _performERC1155Transfer(
                          item.token,
                          item.from,
                          item.to,
                          item.identifier,
                          item.amount
                      );
                  } else {
                      // Throw with an error.
                      revert InvalidItemType();
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          // prettier-ignore
          import {
              ConduitTransfer,
              ConduitBatch1155Transfer
          } from "../conduit/lib/ConduitStructs.sol";
          /**
           * @title ConduitInterface
           * @author 0age
           * @notice ConduitInterface contains all external function interfaces, events,
           *         and errors for conduit contracts.
           */
          interface ConduitInterface {
              /**
               * @dev Revert with an error when attempting to execute transfers using a
               *      caller that does not have an open channel.
               */
              error ChannelClosed(address channel);
              /**
               * @dev Revert with an error when attempting to update a channel to the
               *      current status of that channel.
               */
              error ChannelStatusAlreadySet(address channel, bool isOpen);
              /**
               * @dev Revert with an error when attempting to execute a transfer for an
               *      item that does not have an ERC20/721/1155 item type.
               */
              error InvalidItemType();
              /**
               * @dev Revert with an error when attempting to update the status of a
               *      channel from a caller that is not the conduit controller.
               */
              error InvalidController();
              /**
               * @dev Emit an event whenever a channel is opened or closed.
               *
               * @param channel The channel that has been updated.
               * @param open    A boolean indicating whether the conduit is open or not.
               */
              event ChannelUpdated(address indexed channel, bool open);
              /**
               * @notice Execute a sequence of ERC20/721/1155 transfers. Only a caller
               *         with an open channel can call this function.
               *
               * @param transfers The ERC20/721/1155 transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function execute(ConduitTransfer[] calldata transfers)
                  external
                  returns (bytes4 magicValue);
              /**
               * @notice Execute a sequence of batch 1155 transfers. Only a caller with an
               *         open channel can call this function.
               *
               * @param batch1155Transfers The 1155 batch transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function executeBatch1155(
                  ConduitBatch1155Transfer[] calldata batch1155Transfers
              ) external returns (bytes4 magicValue);
              /**
               * @notice Execute a sequence of transfers, both single and batch 1155. Only
               *         a caller with an open channel can call this function.
               *
               * @param standardTransfers  The ERC20/721/1155 transfers to perform.
               * @param batch1155Transfers The 1155 batch transfers to perform.
               *
               * @return magicValue A magic value indicating that the transfers were
               *                    performed successfully.
               */
              function executeWithBatch1155(
                  ConduitTransfer[] calldata standardTransfers,
                  ConduitBatch1155Transfer[] calldata batch1155Transfers
              ) external returns (bytes4 magicValue);
              /**
               * @notice Open or close a given channel. Only callable by the controller.
               *
               * @param channel The channel to open or close.
               * @param isOpen  The status of the channel (either open or closed).
               */
              function updateChannel(address channel, bool isOpen) external;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          enum ConduitItemType {
              NATIVE, // unused
              ERC20,
              ERC721,
              ERC1155
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          import "./TokenTransferrerConstants.sol";
          // prettier-ignore
          import {
              TokenTransferrerErrors
          } from "../interfaces/TokenTransferrerErrors.sol";
          import { ConduitBatch1155Transfer } from "../conduit/lib/ConduitStructs.sol";
          /**
           * @title TokenTransferrer
           * @author 0age
           * @custom:coauthor d1ll0n
           * @custom:coauthor transmissions11
           * @notice TokenTransferrer is a library for performing optimized ERC20, ERC721,
           *         ERC1155, and batch ERC1155 transfers, used by both Seaport as well as
           *         by conduits deployed by the ConduitController. Use great caution when
           *         considering these functions for use in other codebases, as there are
           *         significant side effects and edge cases that need to be thoroughly
           *         understood and carefully addressed.
           */
          contract TokenTransferrer is TokenTransferrerErrors {
              /**
               * @dev Internal function to transfer ERC20 tokens from a given originator
               *      to a given recipient. Sufficient approvals must be set on the
               *      contract performing the transfer.
               *
               * @param token      The ERC20 token to transfer.
               * @param from       The originator of the transfer.
               * @param to         The recipient of the transfer.
               * @param amount     The amount to transfer.
               */
              function _performERC20Transfer(
                  address token,
                  address from,
                  address to,
                  uint256 amount
              ) internal {
                  // Utilize assembly to perform an optimized ERC20 token transfer.
                  assembly {
                      // The free memory pointer memory slot will be used when populating
                      // call data for the transfer; read the value and restore it later.
                      let memPointer := mload(FreeMemoryPointerSlot)
                      // Write call data into memory, starting with function selector.
                      mstore(ERC20_transferFrom_sig_ptr, ERC20_transferFrom_signature)
                      mstore(ERC20_transferFrom_from_ptr, from)
                      mstore(ERC20_transferFrom_to_ptr, to)
                      mstore(ERC20_transferFrom_amount_ptr, amount)
                      // Make call & copy up to 32 bytes of return data to scratch space.
                      // Scratch space does not need to be cleared ahead of time, as the
                      // subsequent check will ensure that either at least a full word of
                      // return data is received (in which case it will be overwritten) or
                      // that no data is received (in which case scratch space will be
                      // ignored) on a successful call to the given token.
                      let callStatus := call(
                          gas(),
                          token,
                          0,
                          ERC20_transferFrom_sig_ptr,
                          ERC20_transferFrom_length,
                          0,
                          OneWord
                      )
                      // Determine whether transfer was successful using status & result.
                      let success := and(
                          // Set success to whether the call reverted, if not check it
                          // either returned exactly 1 (can't just be non-zero data), or
                          // had no return data.
                          or(
                              and(eq(mload(0), 1), gt(returndatasize(), 31)),
                              iszero(returndatasize())
                          ),
                          callStatus
                      )
                      // Handle cases where either the transfer failed or no data was
                      // returned. Group these, as most transfers will succeed with data.
                      // Equivalent to `or(iszero(success), iszero(returndatasize()))`
                      // but after it's inverted for JUMPI this expression is cheaper.
                      if iszero(and(success, iszero(iszero(returndatasize())))) {
                          // If the token has no code or the transfer failed: Equivalent
                          // to `or(iszero(success), iszero(extcodesize(token)))` but
                          // after it's inverted for JUMPI this expression is cheaper.
                          if iszero(and(iszero(iszero(extcodesize(token))), success)) {
                              // If the transfer failed:
                              if iszero(success) {
                                  // If it was due to a revert:
                                  if iszero(callStatus) {
                                      // If it returned a message, bubble it up as long as
                                      // sufficient gas remains to do so:
                                      if returndatasize() {
                                          // Ensure that sufficient gas is available to
                                          // copy returndata while expanding memory where
                                          // necessary. Start by computing the word size
                                          // of returndata and allocated memory. Round up
                                          // to the nearest full word.
                                          let returnDataWords := div(
                                              add(returndatasize(), AlmostOneWord),
                                              OneWord
                                          )
                                          // Note: use the free memory pointer in place of
                                          // msize() to work around a Yul warning that
                                          // prevents accessing msize directly when the IR
                                          // pipeline is activated.
                                          let msizeWords := div(memPointer, OneWord)
                                          // Next, compute the cost of the returndatacopy.
                                          let cost := mul(CostPerWord, returnDataWords)
                                          // Then, compute cost of new memory allocation.
                                          if gt(returnDataWords, msizeWords) {
                                              cost := add(
                                                  cost,
                                                  add(
                                                      mul(
                                                          sub(
                                                              returnDataWords,
                                                              msizeWords
                                                          ),
                                                          CostPerWord
                                                      ),
                                                      div(
                                                          sub(
                                                              mul(
                                                                  returnDataWords,
                                                                  returnDataWords
                                                              ),
                                                              mul(msizeWords, msizeWords)
                                                          ),
                                                          MemoryExpansionCoefficient
                                                      )
                                                  )
                                              )
                                          }
                                          // Finally, add a small constant and compare to
                                          // gas remaining; bubble up the revert data if
                                          // enough gas is still available.
                                          if lt(add(cost, ExtraGasBuffer), gas()) {
                                              // Copy returndata to memory; overwrite
                                              // existing memory.
                                              returndatacopy(0, 0, returndatasize())
                                              // Revert, specifying memory region with
                                              // copied returndata.
                                              revert(0, returndatasize())
                                          }
                                      }
                                      // Otherwise revert with a generic error message.
                                      mstore(
                                          TokenTransferGenericFailure_error_sig_ptr,
                                          TokenTransferGenericFailure_error_signature
                                      )
                                      mstore(
                                          TokenTransferGenericFailure_error_token_ptr,
                                          token
                                      )
                                      mstore(
                                          TokenTransferGenericFailure_error_from_ptr,
                                          from
                                      )
                                      mstore(TokenTransferGenericFailure_error_to_ptr, to)
                                      mstore(TokenTransferGenericFailure_error_id_ptr, 0)
                                      mstore(
                                          TokenTransferGenericFailure_error_amount_ptr,
                                          amount
                                      )
                                      revert(
                                          TokenTransferGenericFailure_error_sig_ptr,
                                          TokenTransferGenericFailure_error_length
                                      )
                                  }
                                  // Otherwise revert with a message about the token
                                  // returning false or non-compliant return values.
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_sig_ptr,
                                      BadReturnValueFromERC20OnTransfer_error_signature
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_token_ptr,
                                      token
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_from_ptr,
                                      from
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_to_ptr,
                                      to
                                  )
                                  mstore(
                                      BadReturnValueFromERC20OnTransfer_error_amount_ptr,
                                      amount
                                  )
                                  revert(
                                      BadReturnValueFromERC20OnTransfer_error_sig_ptr,
                                      BadReturnValueFromERC20OnTransfer_error_length
                                  )
                              }
                              // Otherwise, revert with error about token not having code:
                              mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                              mstore(NoContract_error_token_ptr, token)
                              revert(NoContract_error_sig_ptr, NoContract_error_length)
                          }
                          // Otherwise, the token just returned no data despite the call
                          // having succeeded; no need to optimize for this as it's not
                          // technically ERC20 compliant.
                      }
                      // Restore the original free memory pointer.
                      mstore(FreeMemoryPointerSlot, memPointer)
                      // Restore the zero slot to zero.
                      mstore(ZeroSlot, 0)
                  }
              }
              /**
               * @dev Internal function to transfer an ERC721 token from a given
               *      originator to a given recipient. Sufficient approvals must be set on
               *      the contract performing the transfer. Note that this function does
               *      not check whether the receiver can accept the ERC721 token (i.e. it
               *      does not use `safeTransferFrom`).
               *
               * @param token      The ERC721 token to transfer.
               * @param from       The originator of the transfer.
               * @param to         The recipient of the transfer.
               * @param identifier The tokenId to transfer.
               */
              function _performERC721Transfer(
                  address token,
                  address from,
                  address to,
                  uint256 identifier
              ) internal {
                  // Utilize assembly to perform an optimized ERC721 token transfer.
                  assembly {
                      // If the token has no code, revert.
                      if iszero(extcodesize(token)) {
                          mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                          mstore(NoContract_error_token_ptr, token)
                          revert(NoContract_error_sig_ptr, NoContract_error_length)
                      }
                      // The free memory pointer memory slot will be used when populating
                      // call data for the transfer; read the value and restore it later.
                      let memPointer := mload(FreeMemoryPointerSlot)
                      // Write call data to memory starting with function selector.
                      mstore(ERC721_transferFrom_sig_ptr, ERC721_transferFrom_signature)
                      mstore(ERC721_transferFrom_from_ptr, from)
                      mstore(ERC721_transferFrom_to_ptr, to)
                      mstore(ERC721_transferFrom_id_ptr, identifier)
                      // Perform the call, ignoring return data.
                      let success := call(
                          gas(),
                          token,
                          0,
                          ERC721_transferFrom_sig_ptr,
                          ERC721_transferFrom_length,
                          0,
                          0
                      )
                      // If the transfer reverted:
                      if iszero(success) {
                          // If it returned a message, bubble it up as long as sufficient
                          // gas remains to do so:
                          if returndatasize() {
                              // Ensure that sufficient gas is available to copy
                              // returndata while expanding memory where necessary. Start
                              // by computing word size of returndata & allocated memory.
                              // Round up to the nearest full word.
                              let returnDataWords := div(
                                  add(returndatasize(), AlmostOneWord),
                                  OneWord
                              )
                              // Note: use the free memory pointer in place of msize() to
                              // work around a Yul warning that prevents accessing msize
                              // directly when the IR pipeline is activated.
                              let msizeWords := div(memPointer, OneWord)
                              // Next, compute the cost of the returndatacopy.
                              let cost := mul(CostPerWord, returnDataWords)
                              // Then, compute cost of new memory allocation.
                              if gt(returnDataWords, msizeWords) {
                                  cost := add(
                                      cost,
                                      add(
                                          mul(
                                              sub(returnDataWords, msizeWords),
                                              CostPerWord
                                          ),
                                          div(
                                              sub(
                                                  mul(returnDataWords, returnDataWords),
                                                  mul(msizeWords, msizeWords)
                                              ),
                                              MemoryExpansionCoefficient
                                          )
                                      )
                                  )
                              }
                              // Finally, add a small constant and compare to gas
                              // remaining; bubble up the revert data if enough gas is
                              // still available.
                              if lt(add(cost, ExtraGasBuffer), gas()) {
                                  // Copy returndata to memory; overwrite existing memory.
                                  returndatacopy(0, 0, returndatasize())
                                  // Revert, giving memory region with copied returndata.
                                  revert(0, returndatasize())
                              }
                          }
                          // Otherwise revert with a generic error message.
                          mstore(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_signature
                          )
                          mstore(TokenTransferGenericFailure_error_token_ptr, token)
                          mstore(TokenTransferGenericFailure_error_from_ptr, from)
                          mstore(TokenTransferGenericFailure_error_to_ptr, to)
                          mstore(TokenTransferGenericFailure_error_id_ptr, identifier)
                          mstore(TokenTransferGenericFailure_error_amount_ptr, 1)
                          revert(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_length
                          )
                      }
                      // Restore the original free memory pointer.
                      mstore(FreeMemoryPointerSlot, memPointer)
                      // Restore the zero slot to zero.
                      mstore(ZeroSlot, 0)
                  }
              }
              /**
               * @dev Internal function to transfer ERC1155 tokens from a given
               *      originator to a given recipient. Sufficient approvals must be set on
               *      the contract performing the transfer and contract recipients must
               *      implement the ERC1155TokenReceiver interface to indicate that they
               *      are willing to accept the transfer.
               *
               * @param token      The ERC1155 token to transfer.
               * @param from       The originator of the transfer.
               * @param to         The recipient of the transfer.
               * @param identifier The id to transfer.
               * @param amount     The amount to transfer.
               */
              function _performERC1155Transfer(
                  address token,
                  address from,
                  address to,
                  uint256 identifier,
                  uint256 amount
              ) internal {
                  // Utilize assembly to perform an optimized ERC1155 token transfer.
                  assembly {
                      // If the token has no code, revert.
                      if iszero(extcodesize(token)) {
                          mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                          mstore(NoContract_error_token_ptr, token)
                          revert(NoContract_error_sig_ptr, NoContract_error_length)
                      }
                      // The following memory slots will be used when populating call data
                      // for the transfer; read the values and restore them later.
                      let memPointer := mload(FreeMemoryPointerSlot)
                      let slot0x80 := mload(Slot0x80)
                      let slot0xA0 := mload(Slot0xA0)
                      let slot0xC0 := mload(Slot0xC0)
                      // Write call data into memory, beginning with function selector.
                      mstore(
                          ERC1155_safeTransferFrom_sig_ptr,
                          ERC1155_safeTransferFrom_signature
                      )
                      mstore(ERC1155_safeTransferFrom_from_ptr, from)
                      mstore(ERC1155_safeTransferFrom_to_ptr, to)
                      mstore(ERC1155_safeTransferFrom_id_ptr, identifier)
                      mstore(ERC1155_safeTransferFrom_amount_ptr, amount)
                      mstore(
                          ERC1155_safeTransferFrom_data_offset_ptr,
                          ERC1155_safeTransferFrom_data_length_offset
                      )
                      mstore(ERC1155_safeTransferFrom_data_length_ptr, 0)
                      // Perform the call, ignoring return data.
                      let success := call(
                          gas(),
                          token,
                          0,
                          ERC1155_safeTransferFrom_sig_ptr,
                          ERC1155_safeTransferFrom_length,
                          0,
                          0
                      )
                      // If the transfer reverted:
                      if iszero(success) {
                          // If it returned a message, bubble it up as long as sufficient
                          // gas remains to do so:
                          if returndatasize() {
                              // Ensure that sufficient gas is available to copy
                              // returndata while expanding memory where necessary. Start
                              // by computing word size of returndata & allocated memory.
                              // Round up to the nearest full word.
                              let returnDataWords := div(
                                  add(returndatasize(), AlmostOneWord),
                                  OneWord
                              )
                              // Note: use the free memory pointer in place of msize() to
                              // work around a Yul warning that prevents accessing msize
                              // directly when the IR pipeline is activated.
                              let msizeWords := div(memPointer, OneWord)
                              // Next, compute the cost of the returndatacopy.
                              let cost := mul(CostPerWord, returnDataWords)
                              // Then, compute cost of new memory allocation.
                              if gt(returnDataWords, msizeWords) {
                                  cost := add(
                                      cost,
                                      add(
                                          mul(
                                              sub(returnDataWords, msizeWords),
                                              CostPerWord
                                          ),
                                          div(
                                              sub(
                                                  mul(returnDataWords, returnDataWords),
                                                  mul(msizeWords, msizeWords)
                                              ),
                                              MemoryExpansionCoefficient
                                          )
                                      )
                                  )
                              }
                              // Finally, add a small constant and compare to gas
                              // remaining; bubble up the revert data if enough gas is
                              // still available.
                              if lt(add(cost, ExtraGasBuffer), gas()) {
                                  // Copy returndata to memory; overwrite existing memory.
                                  returndatacopy(0, 0, returndatasize())
                                  // Revert, giving memory region with copied returndata.
                                  revert(0, returndatasize())
                              }
                          }
                          // Otherwise revert with a generic error message.
                          mstore(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_signature
                          )
                          mstore(TokenTransferGenericFailure_error_token_ptr, token)
                          mstore(TokenTransferGenericFailure_error_from_ptr, from)
                          mstore(TokenTransferGenericFailure_error_to_ptr, to)
                          mstore(TokenTransferGenericFailure_error_id_ptr, identifier)
                          mstore(TokenTransferGenericFailure_error_amount_ptr, amount)
                          revert(
                              TokenTransferGenericFailure_error_sig_ptr,
                              TokenTransferGenericFailure_error_length
                          )
                      }
                      mstore(Slot0x80, slot0x80) // Restore slot 0x80.
                      mstore(Slot0xA0, slot0xA0) // Restore slot 0xA0.
                      mstore(Slot0xC0, slot0xC0) // Restore slot 0xC0.
                      // Restore the original free memory pointer.
                      mstore(FreeMemoryPointerSlot, memPointer)
                      // Restore the zero slot to zero.
                      mstore(ZeroSlot, 0)
                  }
              }
              /**
               * @dev Internal function to transfer ERC1155 tokens from a given
               *      originator to a given recipient. Sufficient approvals must be set on
               *      the contract performing the transfer and contract recipients must
               *      implement the ERC1155TokenReceiver interface to indicate that they
               *      are willing to accept the transfer. NOTE: this function is not
               *      memory-safe; it will overwrite existing memory, restore the free
               *      memory pointer to the default value, and overwrite the zero slot.
               *      This function should only be called once memory is no longer
               *      required and when uninitialized arrays are not utilized, and memory
               *      should be considered fully corrupted (aside from the existence of a
               *      default-value free memory pointer) after calling this function.
               *
               * @param batchTransfers The group of 1155 batch transfers to perform.
               */
              function _performERC1155BatchTransfers(
                  ConduitBatch1155Transfer[] calldata batchTransfers
              ) internal {
                  // Utilize assembly to perform optimized batch 1155 transfers.
                  assembly {
                      let len := batchTransfers.length
                      // Pointer to first head in the array, which is offset to the struct
                      // at each index. This gets incremented after each loop to avoid
                      // multiplying by 32 to get the offset for each element.
                      let nextElementHeadPtr := batchTransfers.offset
                      // Pointer to beginning of the head of the array. This is the
                      // reference position each offset references. It's held static to
                      // let each loop calculate the data position for an element.
                      let arrayHeadPtr := nextElementHeadPtr
                      // Write the function selector, which will be reused for each call:
                      // safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
                      mstore(
                          ConduitBatch1155Transfer_from_offset,
                          ERC1155_safeBatchTransferFrom_signature
                      )
                      // Iterate over each batch transfer.
                      for {
                          let i := 0
                      } lt(i, len) {
                          i := add(i, 1)
                      } {
                          // Read the offset to the beginning of the element and add
                          // it to pointer to the beginning of the array head to get
                          // the absolute position of the element in calldata.
                          let elementPtr := add(
                              arrayHeadPtr,
                              calldataload(nextElementHeadPtr)
                          )
                          // Retrieve the token from calldata.
                          let token := calldataload(elementPtr)
                          // If the token has no code, revert.
                          if iszero(extcodesize(token)) {
                              mstore(NoContract_error_sig_ptr, NoContract_error_signature)
                              mstore(NoContract_error_token_ptr, token)
                              revert(NoContract_error_sig_ptr, NoContract_error_length)
                          }
                          // Get the total number of supplied ids.
                          let idsLength := calldataload(
                              add(elementPtr, ConduitBatch1155Transfer_ids_length_offset)
                          )
                          // Determine the expected offset for the amounts array.
                          let expectedAmountsOffset := add(
                              ConduitBatch1155Transfer_amounts_length_baseOffset,
                              mul(idsLength, OneWord)
                          )
                          // Validate struct encoding.
                          let invalidEncoding := iszero(
                              and(
                                  // ids.length == amounts.length
                                  eq(
                                      idsLength,
                                      calldataload(add(elementPtr, expectedAmountsOffset))
                                  ),
                                  and(
                                      // ids_offset == 0xa0
                                      eq(
                                          calldataload(
                                              add(
                                                  elementPtr,
                                                  ConduitBatch1155Transfer_ids_head_offset
                                              )
                                          ),
                                          ConduitBatch1155Transfer_ids_length_offset
                                      ),
                                      // amounts_offset == 0xc0 + ids.length*32
                                      eq(
                                          calldataload(
                                              add(
                                                  elementPtr,
                                                  ConduitBatchTransfer_amounts_head_offset
                                              )
                                          ),
                                          expectedAmountsOffset
                                      )
                                  )
                              )
                          )
                          // Revert with an error if the encoding is not valid.
                          if invalidEncoding {
                              mstore(
                                  Invalid1155BatchTransferEncoding_ptr,
                                  Invalid1155BatchTransferEncoding_selector
                              )
                              revert(
                                  Invalid1155BatchTransferEncoding_ptr,
                                  Invalid1155BatchTransferEncoding_length
                              )
                          }
                          // Update the offset position for the next loop
                          nextElementHeadPtr := add(nextElementHeadPtr, OneWord)
                          // Copy the first section of calldata (before dynamic values).
                          calldatacopy(
                              BatchTransfer1155Params_ptr,
                              add(elementPtr, ConduitBatch1155Transfer_from_offset),
                              ConduitBatch1155Transfer_usable_head_size
                          )
                          // Determine size of calldata required for ids and amounts. Note
                          // that the size includes both lengths as well as the data.
                          let idsAndAmountsSize := add(TwoWords, mul(idsLength, TwoWords))
                          // Update the offset for the data array in memory.
                          mstore(
                              BatchTransfer1155Params_data_head_ptr,
                              add(
                                  BatchTransfer1155Params_ids_length_offset,
                                  idsAndAmountsSize
                              )
                          )
                          // Set the length of the data array in memory to zero.
                          mstore(
                              add(
                                  BatchTransfer1155Params_data_length_basePtr,
                                  idsAndAmountsSize
                              ),
                              0
                          )
                          // Determine the total calldata size for the call to transfer.
                          let transferDataSize := add(
                              BatchTransfer1155Params_calldata_baseSize,
                              idsAndAmountsSize
                          )
                          // Copy second section of calldata (including dynamic values).
                          calldatacopy(
                              BatchTransfer1155Params_ids_length_ptr,
                              add(elementPtr, ConduitBatch1155Transfer_ids_length_offset),
                              idsAndAmountsSize
                          )
                          // Perform the call to transfer 1155 tokens.
                          let success := call(
                              gas(),
                              token,
                              0,
                              ConduitBatch1155Transfer_from_offset, // Data portion start.
                              transferDataSize, // Location of the length of callData.
                              0,
                              0
                          )
                          // If the transfer reverted:
                          if iszero(success) {
                              // If it returned a message, bubble it up as long as
                              // sufficient gas remains to do so:
                              if returndatasize() {
                                  // Ensure that sufficient gas is available to copy
                                  // returndata while expanding memory where necessary.
                                  // Start by computing word size of returndata and
                                  // allocated memory. Round up to the nearest full word.
                                  let returnDataWords := div(
                                      add(returndatasize(), AlmostOneWord),
                                      OneWord
                                  )
                                  // Note: use transferDataSize in place of msize() to
                                  // work around a Yul warning that prevents accessing
                                  // msize directly when the IR pipeline is activated.
                                  // The free memory pointer is not used here because
                                  // this function does almost all memory management
                                  // manually and does not update it, and transferDataSize
                                  // should be the largest memory value used (unless a
                                  // previous batch was larger).
                                  let msizeWords := div(transferDataSize, OneWord)
                                  // Next, compute the cost of the returndatacopy.
                                  let cost := mul(CostPerWord, returnDataWords)
                                  // Then, compute cost of new memory allocation.
                                  if gt(returnDataWords, msizeWords) {
                                      cost := add(
                                          cost,
                                          add(
                                              mul(
                                                  sub(returnDataWords, msizeWords),
                                                  CostPerWord
                                              ),
                                              div(
                                                  sub(
                                                      mul(
                                                          returnDataWords,
                                                          returnDataWords
                                                      ),
                                                      mul(msizeWords, msizeWords)
                                                  ),
                                                  MemoryExpansionCoefficient
                                              )
                                          )
                                      )
                                  }
                                  // Finally, add a small constant and compare to gas
                                  // remaining; bubble up the revert data if enough gas is
                                  // still available.
                                  if lt(add(cost, ExtraGasBuffer), gas()) {
                                      // Copy returndata to memory; overwrite existing.
                                      returndatacopy(0, 0, returndatasize())
                                      // Revert with memory region containing returndata.
                                      revert(0, returndatasize())
                                  }
                              }
                              // Set the error signature.
                              mstore(
                                  0,
                                  ERC1155BatchTransferGenericFailure_error_signature
                              )
                              // Write the token.
                              mstore(ERC1155BatchTransferGenericFailure_token_ptr, token)
                              // Increase the offset to ids by 32.
                              mstore(
                                  BatchTransfer1155Params_ids_head_ptr,
                                  ERC1155BatchTransferGenericFailure_ids_offset
                              )
                              // Increase the offset to amounts by 32.
                              mstore(
                                  BatchTransfer1155Params_amounts_head_ptr,
                                  add(
                                      OneWord,
                                      mload(BatchTransfer1155Params_amounts_head_ptr)
                                  )
                              )
                              // Return modified region. The total size stays the same as
                              // `token` uses the same number of bytes as `data.length`.
                              revert(0, transferDataSize)
                          }
                      }
                      // Reset the free memory pointer to the default value; memory must
                      // be assumed to be dirtied and not reused from this point forward.
                      // Also note that the zero slot is not reset to zero, meaning empty
                      // arrays cannot be safely created or utilized until it is restored.
                      mstore(FreeMemoryPointerSlot, DefaultFreeMemoryPointer)
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          import { ConduitItemType } from "./ConduitEnums.sol";
          struct ConduitTransfer {
              ConduitItemType itemType;
              address token;
              address from;
              address to;
              uint256 identifier;
              uint256 amount;
          }
          struct ConduitBatch1155Transfer {
              address token;
              address from;
              address to;
              uint256[] ids;
              uint256[] amounts;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          // error ChannelClosed(address channel)
          uint256 constant ChannelClosed_error_signature = (
              0x93daadf200000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ChannelClosed_error_ptr = 0x00;
          uint256 constant ChannelClosed_channel_ptr = 0x4;
          uint256 constant ChannelClosed_error_length = 0x24;
          // For the mapping:
          // mapping(address => bool) channels
          // The position in storage for a particular account is:
          // keccak256(abi.encode(account, channels.slot))
          uint256 constant ChannelKey_channel_ptr = 0x00;
          uint256 constant ChannelKey_slot_ptr = 0x20;
          uint256 constant ChannelKey_length = 0x40;
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          /*
           * -------------------------- Disambiguation & Other Notes ---------------------
           *    - The term "head" is used as it is in the documentation for ABI encoding,
           *      but only in reference to dynamic types, i.e. it always refers to the
           *      offset or pointer to the body of a dynamic type. In calldata, the head
           *      is always an offset (relative to the parent object), while in memory,
           *      the head is always the pointer to the body. More information found here:
           *      https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#argument-encoding
           *        - Note that the length of an array is separate from and precedes the
           *          head of the array.
           *
           *    - The term "body" is used in place of the term "head" used in the ABI
           *      documentation. It refers to the start of the data for a dynamic type,
           *      e.g. the first word of a struct or the first word of the first element
           *      in an array.
           *
           *    - The term "pointer" is used to describe the absolute position of a value
           *      and never an offset relative to another value.
           *        - The suffix "_ptr" refers to a memory pointer.
           *        - The suffix "_cdPtr" refers to a calldata pointer.
           *
           *    - The term "offset" is used to describe the position of a value relative
           *      to some parent value. For example, OrderParameters_conduit_offset is the
           *      offset to the "conduit" value in the OrderParameters struct relative to
           *      the start of the body.
           *        - Note: Offsets are used to derive pointers.
           *
           *    - Some structs have pointers defined for all of their fields in this file.
           *      Lines which are commented out are fields that are not used in the
           *      codebase but have been left in for readability.
           */
          uint256 constant AlmostOneWord = 0x1f;
          uint256 constant OneWord = 0x20;
          uint256 constant TwoWords = 0x40;
          uint256 constant ThreeWords = 0x60;
          uint256 constant FreeMemoryPointerSlot = 0x40;
          uint256 constant ZeroSlot = 0x60;
          uint256 constant DefaultFreeMemoryPointer = 0x80;
          uint256 constant Slot0x80 = 0x80;
          uint256 constant Slot0xA0 = 0xa0;
          uint256 constant Slot0xC0 = 0xc0;
          // abi.encodeWithSignature("transferFrom(address,address,uint256)")
          uint256 constant ERC20_transferFrom_signature = (
              0x23b872dd00000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC20_transferFrom_sig_ptr = 0x0;
          uint256 constant ERC20_transferFrom_from_ptr = 0x04;
          uint256 constant ERC20_transferFrom_to_ptr = 0x24;
          uint256 constant ERC20_transferFrom_amount_ptr = 0x44;
          uint256 constant ERC20_transferFrom_length = 0x64; // 4 + 32 * 3 == 100
          // abi.encodeWithSignature(
          //     "safeTransferFrom(address,address,uint256,uint256,bytes)"
          // )
          uint256 constant ERC1155_safeTransferFrom_signature = (
              0xf242432a00000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC1155_safeTransferFrom_sig_ptr = 0x0;
          uint256 constant ERC1155_safeTransferFrom_from_ptr = 0x04;
          uint256 constant ERC1155_safeTransferFrom_to_ptr = 0x24;
          uint256 constant ERC1155_safeTransferFrom_id_ptr = 0x44;
          uint256 constant ERC1155_safeTransferFrom_amount_ptr = 0x64;
          uint256 constant ERC1155_safeTransferFrom_data_offset_ptr = 0x84;
          uint256 constant ERC1155_safeTransferFrom_data_length_ptr = 0xa4;
          uint256 constant ERC1155_safeTransferFrom_length = 0xc4; // 4 + 32 * 6 == 196
          uint256 constant ERC1155_safeTransferFrom_data_length_offset = 0xa0;
          // abi.encodeWithSignature(
          //     "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"
          // )
          uint256 constant ERC1155_safeBatchTransferFrom_signature = (
              0x2eb2c2d600000000000000000000000000000000000000000000000000000000
          );
          bytes4 constant ERC1155_safeBatchTransferFrom_selector = bytes4(
              bytes32(ERC1155_safeBatchTransferFrom_signature)
          );
          uint256 constant ERC721_transferFrom_signature = ERC20_transferFrom_signature;
          uint256 constant ERC721_transferFrom_sig_ptr = 0x0;
          uint256 constant ERC721_transferFrom_from_ptr = 0x04;
          uint256 constant ERC721_transferFrom_to_ptr = 0x24;
          uint256 constant ERC721_transferFrom_id_ptr = 0x44;
          uint256 constant ERC721_transferFrom_length = 0x64; // 4 + 32 * 3 == 100
          // abi.encodeWithSignature("NoContract(address)")
          uint256 constant NoContract_error_signature = (
              0x5f15d67200000000000000000000000000000000000000000000000000000000
          );
          uint256 constant NoContract_error_sig_ptr = 0x0;
          uint256 constant NoContract_error_token_ptr = 0x4;
          uint256 constant NoContract_error_length = 0x24; // 4 + 32 == 36
          // abi.encodeWithSignature(
          //     "TokenTransferGenericFailure(address,address,address,uint256,uint256)"
          // )
          uint256 constant TokenTransferGenericFailure_error_signature = (
              0xf486bc8700000000000000000000000000000000000000000000000000000000
          );
          uint256 constant TokenTransferGenericFailure_error_sig_ptr = 0x0;
          uint256 constant TokenTransferGenericFailure_error_token_ptr = 0x4;
          uint256 constant TokenTransferGenericFailure_error_from_ptr = 0x24;
          uint256 constant TokenTransferGenericFailure_error_to_ptr = 0x44;
          uint256 constant TokenTransferGenericFailure_error_id_ptr = 0x64;
          uint256 constant TokenTransferGenericFailure_error_amount_ptr = 0x84;
          // 4 + 32 * 5 == 164
          uint256 constant TokenTransferGenericFailure_error_length = 0xa4;
          // abi.encodeWithSignature(
          //     "BadReturnValueFromERC20OnTransfer(address,address,address,uint256)"
          // )
          uint256 constant BadReturnValueFromERC20OnTransfer_error_signature = (
              0x9889192300000000000000000000000000000000000000000000000000000000
          );
          uint256 constant BadReturnValueFromERC20OnTransfer_error_sig_ptr = 0x0;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_token_ptr = 0x4;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_from_ptr = 0x24;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_to_ptr = 0x44;
          uint256 constant BadReturnValueFromERC20OnTransfer_error_amount_ptr = 0x64;
          // 4 + 32 * 4 == 132
          uint256 constant BadReturnValueFromERC20OnTransfer_error_length = 0x84;
          uint256 constant ExtraGasBuffer = 0x20;
          uint256 constant CostPerWord = 3;
          uint256 constant MemoryExpansionCoefficient = 0x200;
          // Values are offset by 32 bytes in order to write the token to the beginning
          // in the event of a revert
          uint256 constant BatchTransfer1155Params_ptr = 0x24;
          uint256 constant BatchTransfer1155Params_ids_head_ptr = 0x64;
          uint256 constant BatchTransfer1155Params_amounts_head_ptr = 0x84;
          uint256 constant BatchTransfer1155Params_data_head_ptr = 0xa4;
          uint256 constant BatchTransfer1155Params_data_length_basePtr = 0xc4;
          uint256 constant BatchTransfer1155Params_calldata_baseSize = 0xc4;
          uint256 constant BatchTransfer1155Params_ids_length_ptr = 0xc4;
          uint256 constant BatchTransfer1155Params_ids_length_offset = 0xa0;
          uint256 constant BatchTransfer1155Params_amounts_length_baseOffset = 0xc0;
          uint256 constant BatchTransfer1155Params_data_length_baseOffset = 0xe0;
          uint256 constant ConduitBatch1155Transfer_usable_head_size = 0x80;
          uint256 constant ConduitBatch1155Transfer_from_offset = 0x20;
          uint256 constant ConduitBatch1155Transfer_ids_head_offset = 0x60;
          uint256 constant ConduitBatch1155Transfer_amounts_head_offset = 0x80;
          uint256 constant ConduitBatch1155Transfer_ids_length_offset = 0xa0;
          uint256 constant ConduitBatch1155Transfer_amounts_length_baseOffset = 0xc0;
          uint256 constant ConduitBatch1155Transfer_calldata_baseSize = 0xc0;
          // Note: abbreviated version of above constant to adhere to line length limit.
          uint256 constant ConduitBatchTransfer_amounts_head_offset = 0x80;
          uint256 constant Invalid1155BatchTransferEncoding_ptr = 0x00;
          uint256 constant Invalid1155BatchTransferEncoding_length = 0x04;
          uint256 constant Invalid1155BatchTransferEncoding_selector = (
              0xeba2084c00000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC1155BatchTransferGenericFailure_error_signature = (
              0xafc445e200000000000000000000000000000000000000000000000000000000
          );
          uint256 constant ERC1155BatchTransferGenericFailure_token_ptr = 0x04;
          uint256 constant ERC1155BatchTransferGenericFailure_ids_offset = 0xc0;
          // SPDX-License-Identifier: MIT
          pragma solidity >=0.8.7;
          /**
           * @title TokenTransferrerErrors
           */
          interface TokenTransferrerErrors {
              /**
               * @dev Revert with an error when an ERC721 transfer with amount other than
               *      one is attempted.
               */
              error InvalidERC721TransferAmount();
              /**
               * @dev Revert with an error when attempting to fulfill an order where an
               *      item has an amount of zero.
               */
              error MissingItemAmount();
              /**
               * @dev Revert with an error when attempting to fulfill an order where an
               *      item has unused parameters. This includes both the token and the
               *      identifier parameters for native transfers as well as the identifier
               *      parameter for ERC20 transfers. Note that the conduit does not
               *      perform this check, leaving it up to the calling channel to enforce
               *      when desired.
               */
              error UnusedItemParameters();
              /**
               * @dev Revert with an error when an ERC20, ERC721, or ERC1155 token
               *      transfer reverts.
               *
               * @param token      The token for which the transfer was attempted.
               * @param from       The source of the attempted transfer.
               * @param to         The recipient of the attempted transfer.
               * @param identifier The identifier for the attempted transfer.
               * @param amount     The amount for the attempted transfer.
               */
              error TokenTransferGenericFailure(
                  address token,
                  address from,
                  address to,
                  uint256 identifier,
                  uint256 amount
              );
              /**
               * @dev Revert with an error when a batch ERC1155 token transfer reverts.
               *
               * @param token       The token for which the transfer was attempted.
               * @param from        The source of the attempted transfer.
               * @param to          The recipient of the attempted transfer.
               * @param identifiers The identifiers for the attempted transfer.
               * @param amounts     The amounts for the attempted transfer.
               */
              error ERC1155BatchTransferGenericFailure(
                  address token,
                  address from,
                  address to,
                  uint256[] identifiers,
                  uint256[] amounts
              );
              /**
               * @dev Revert with an error when an ERC20 token transfer returns a falsey
               *      value.
               *
               * @param token      The token for which the ERC20 transfer was attempted.
               * @param from       The source of the attempted ERC20 transfer.
               * @param to         The recipient of the attempted ERC20 transfer.
               * @param amount     The amount for the attempted ERC20 transfer.
               */
              error BadReturnValueFromERC20OnTransfer(
                  address token,
                  address from,
                  address to,
                  uint256 amount
              );
              /**
               * @dev Revert with an error when an account being called as an assumed
               *      contract does not have code and returns no data.
               *
               * @param account The account that should contain code.
               */
              error NoContract(address account);
              /**
               * @dev Revert with an error when attempting to execute an 1155 batch
               *      transfer using calldata not produced by default ABI encoding or with
               *      different lengths for ids and amounts arrays.
               */
              error Invalid1155BatchTransferEncoding();
          }
          

          File 4 of 6: AddressRelay
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              constructor() {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // 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
          pragma solidity 0.8.18;
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {IAddressRelay, Implementation} from "./interfaces/IAddressRelay.sol";
          import {IERC165} from "./interfaces/IERC165.sol";
          import {IERC173} from "./interfaces/IERC173.sol";
          /**
           * @author Created by HeyMint Launchpad https://join.heymint.xyz
           * @notice This contract contains the base logic for ERC-721A tokens deployed with HeyMint
           */
          contract AddressRelay is IAddressRelay, Ownable {
              mapping(bytes4 => address) public selectorToImplAddress;
              mapping(bytes4 => bool) public supportedInterfaces;
              bytes4[] selectors;
              address[] implAddresses;
              address public fallbackImplAddress;
              bool public relayFrozen;
              constructor() {
                  supportedInterfaces[0x01ffc9a7] = true; // IERC165
                  supportedInterfaces[0x7f5828d0] = true; // IERC173
                  supportedInterfaces[0x80ac58cd] = true; // IERC721
                  supportedInterfaces[0x5b5e139f] = true; // IERC721Metadata
                  supportedInterfaces[0x2a55205a] = true; // IERC2981
                  supportedInterfaces[0xad092b5c] = true; // IERC4907
              }
              /**
               * @notice Permanently freezes the relay so no more selectors can be added or removed
               */
              function freezeRelay() external onlyOwner {
                  relayFrozen = true;
              }
              /**
               * @notice Adds or updates selectors and their implementation addresses
               * @param _selectors The selectors to add or update
               * @param _implAddress The implementation address the selectors will point to
               */
              function addOrUpdateSelectors(
                  bytes4[] memory _selectors,
                  address _implAddress
              ) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  for (uint256 i = 0; i < _selectors.length; i++) {
                      bytes4 selector = _selectors[i];
                      selectorToImplAddress[selector] = _implAddress;
                      selectors.push(selector);
                  }
                  bool implAddressExists = false;
                  for (uint256 i = 0; i < implAddresses.length; i++) {
                      if (implAddresses[i] == _implAddress) {
                          implAddressExists = true;
                          break;
                      }
                  }
                  if (!implAddressExists) {
                      implAddresses.push(_implAddress);
                  }
              }
              /**
               * @notice Removes selectors
               * @param _selectors The selectors to remove
               */
              function removeSelectors(bytes4[] memory _selectors) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  for (uint256 i = 0; i < _selectors.length; i++) {
                      bytes4 selector = _selectors[i];
                      delete selectorToImplAddress[selector];
                      for (uint256 j = 0; j < selectors.length; j++) {
                          if (selectors[j] == selector) {
                              // this just sets the value to 0, but doesn't remove it from the array
                              delete selectors[j];
                              break;
                          }
                      }
                  }
              }
              /**
               * @notice Removes an implementation address and all the selectors that point to it
               * @param _implAddress The implementation address to remove
               */
              function removeImplAddressAndAllSelectors(
                  address _implAddress
              ) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  for (uint256 i = 0; i < implAddresses.length; i++) {
                      if (implAddresses[i] == _implAddress) {
                          // this just sets the value to 0, but doesn't remove it from the array
                          delete implAddresses[i];
                          break;
                      }
                  }
                  for (uint256 i = 0; i < selectors.length; i++) {
                      if (selectorToImplAddress[selectors[i]] == _implAddress) {
                          delete selectorToImplAddress[selectors[i]];
                          delete selectors[i];
                      }
                  }
              }
              /**
               * @notice Returns the implementation address for a given function selector
               * @param _functionSelector The function selector to get the implementation address for
               */
              function getImplAddress(
                  bytes4 _functionSelector
              ) external view returns (address) {
                  address implAddress = selectorToImplAddress[_functionSelector];
                  if (implAddress == address(0)) {
                      implAddress = fallbackImplAddress;
                  }
                  require(implAddress != address(0), "Function does not exist");
                  return implAddress;
              }
              /**
               * @notice Returns the implementation address for a given function selector. Throws an error if function does not exist.
               * @param _functionSelector The function selector to get the implementation address for
               */
              function getImplAddressNoFallback(
                  bytes4 _functionSelector
              ) external view returns (address) {
                  address implAddress = selectorToImplAddress[_functionSelector];
                  require(implAddress != address(0), "Function does not exist");
                  return implAddress;
              }
              /**
               * @notice Returns all the implementation addresses and the selectors they support
               * @return impls_ An array of Implementation structs
               */
              function getAllImplAddressesAndSelectors()
                  external
                  view
                  returns (Implementation[] memory)
              {
                  uint256 trueImplAddressCount = 0;
                  uint256 implAddressesLength = implAddresses.length;
                  for (uint256 i = 0; i < implAddressesLength; i++) {
                      if (implAddresses[i] != address(0)) {
                          trueImplAddressCount++;
                      }
                  }
                  Implementation[] memory impls = new Implementation[](
                      trueImplAddressCount
                  );
                  for (uint256 i = 0; i < implAddressesLength; i++) {
                      if (implAddresses[i] == address(0)) {
                          continue;
                      }
                      address implAddress = implAddresses[i];
                      bytes4[] memory selectors_;
                      uint256 selectorCount = 0;
                      uint256 selectorsLength = selectors.length;
                      for (uint256 j = 0; j < selectorsLength; j++) {
                          if (selectorToImplAddress[selectors[j]] == implAddress) {
                              selectorCount++;
                          }
                      }
                      selectors_ = new bytes4[](selectorCount);
                      uint256 selectorIndex = 0;
                      for (uint256 j = 0; j < selectorsLength; j++) {
                          if (selectorToImplAddress[selectors[j]] == implAddress) {
                              selectors_[selectorIndex] = selectors[j];
                              selectorIndex++;
                          }
                      }
                      impls[i] = Implementation(implAddress, selectors_);
                  }
                  return impls;
              }
              /**
               * @notice Return all the function selectors associated with an implementation address
               * @param _implAddress The implementation address to get the selectors for
               */
              function getSelectorsForImplAddress(
                  address _implAddress
              ) external view returns (bytes4[] memory) {
                  uint256 selectorCount = 0;
                  uint256 selectorsLength = selectors.length;
                  for (uint256 i = 0; i < selectorsLength; i++) {
                      if (selectorToImplAddress[selectors[i]] == _implAddress) {
                          selectorCount++;
                      }
                  }
                  bytes4[] memory selectorArr = new bytes4[](selectorCount);
                  uint256 selectorIndex = 0;
                  for (uint256 i = 0; i < selectorsLength; i++) {
                      if (selectorToImplAddress[selectors[i]] == _implAddress) {
                          selectorArr[selectorIndex] = selectors[i];
                          selectorIndex++;
                      }
                  }
                  return selectorArr;
              }
              /**
               * @notice Sets the fallback implementation address to use when a function selector is not found
               * @param _fallbackAddress The fallback implementation address
               */
              function setFallbackImplAddress(
                  address _fallbackAddress
              ) external onlyOwner {
                  require(!relayFrozen, "RELAY_FROZEN");
                  fallbackImplAddress = _fallbackAddress;
              }
              /**
               * @notice Updates the supported interfaces
               * @param _interfaceId The interface ID to update
               * @param _supported Whether the interface is supported or not
               */
              function updateSupportedInterfaces(
                  bytes4 _interfaceId,
                  bool _supported
              ) external onlyOwner {
                  supportedInterfaces[_interfaceId] = _supported;
              }
              /**
               * @notice Returns whether the interface is supported or not
               * @param _interfaceId The interface ID to check
               */
              function supportsInterface(
                  bytes4 _interfaceId
              ) external view returns (bool) {
                  return supportedInterfaces[_interfaceId];
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          struct Implementation {
              address implAddress;
              bytes4[] selectors;
          }
          interface IAddressRelay {
              /**
               * @notice Returns the fallback implementation address
               */
              function fallbackImplAddress() external returns (address);
              /**
               * @notice Adds or updates selectors and their implementation addresses
               * @param _selectors The selectors to add or update
               * @param _implAddress The implementation address the selectors will point to
               */
              function addOrUpdateSelectors(
                  bytes4[] memory _selectors,
                  address _implAddress
              ) external;
              /**
               * @notice Removes selectors
               * @param _selectors The selectors to remove
               */
              function removeSelectors(bytes4[] memory _selectors) external;
              /**
               * @notice Removes an implementation address and all the selectors that point to it
               * @param _implAddress The implementation address to remove
               */
              function removeImplAddressAndAllSelectors(address _implAddress) external;
              /**
               * @notice Returns the implementation address for a given function selector
               * @param _functionSelector The function selector to get the implementation address for
               */
              function getImplAddress(
                  bytes4 _functionSelector
              ) external view returns (address implAddress_);
              /**
               * @notice Returns all the implementation addresses and the selectors they support
               * @return impls_ An array of Implementation structs
               */
              function getAllImplAddressesAndSelectors()
                  external
                  view
                  returns (Implementation[] memory impls_);
              /**
               * @notice Return all the fucntion selectors associated with an implementation address
               * @param _implAddress The implementation address to get the selectors for
               */
              function getSelectorsForImplAddress(
                  address _implAddress
              ) external view returns (bytes4[] memory selectors_);
              /**
               * @notice Sets the fallback implementation address to use when a function selector is not found
               * @param _fallbackAddress The fallback implementation address
               */
              function setFallbackImplAddress(address _fallbackAddress) external;
              /**
               * @notice Updates the supported interfaces
               * @param _interfaceId The interface ID to update
               * @param _supported Whether the interface is supported or not
               */
              function updateSupportedInterfaces(
                  bytes4 _interfaceId,
                  bool _supported
              ) external;
              /**
               * @notice Returns whether the interface is supported or not
               * @param _interfaceId The interface ID to check
               */
              function supportsInterface(
                  bytes4 _interfaceId
              ) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          interface IERC165 {
              /// @notice Query if a contract implements an interface
              /// @param interfaceId The interface identifier, as specified in ERC-165
              /// @dev Interface identification is specified in ERC-165. This function
              ///  uses less than 30,000 gas.
              /// @return `true` if the contract implements `interfaceID` and
              ///  `interfaceID` is not 0xffffffff, `false` otherwise
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.18;
          /// @title ERC-173 Contract Ownership Standard
          ///  Note: the ERC-165 identifier for this interface is 0x7f5828d0
          /* is ERC165 */
          interface IERC173 {
              /// @dev This emits when ownership of a contract changes.
              event OwnershipTransferred(
                  address indexed previousOwner,
                  address indexed newOwner
              );
              /// @notice Get the address of the owner
              /// @return owner_ The address of the owner.
              function owner() external view returns (address owner_);
              /// @notice Set the address of the new owner of the contract
              /// @dev Set _newOwner to address(0) to renounce any ownership.
              /// @param _newOwner The address of the new owner of the contract
              function transferOwnership(address _newOwner) external;
          }
          

          File 5 of 6: HeyMintERC721ABase
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/ContextUpgradeable.sol";
          import "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              function __Ownable_init() internal onlyInitializing {
                  __Ownable_init_unchained();
              }
              function __Ownable_init_unchained() internal onlyInitializing {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)
          pragma solidity ^0.8.0;
          import "../utils/introspection/IERC165Upgradeable.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 IERC2981Upgradeable is IERC165Upgradeable {
              /**
               * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
               * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
               */
              function royaltyInfo(uint256 tokenId, uint256 salePrice)
                  external
                  view
                  returns (address receiver, uint256 royaltyAmount);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.1) (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 Returns the highest version that has been initialized. See {reinitializer}.
               */
              function _getInitializedVersion() internal view returns (uint8) {
                  return _initialized;
              }
              /**
               * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
               */
              function _isInitializing() internal view returns (bool) {
                  return _initializing;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/ContextUpgradeable.sol";
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
              /**
               * @dev Emitted when the pause is triggered by `account`.
               */
              event Paused(address account);
              /**
               * @dev Emitted when the pause is lifted by `account`.
               */
              event Unpaused(address account);
              bool private _paused;
              /**
               * @dev Initializes the contract in unpaused state.
               */
              function __Pausable_init() internal onlyInitializing {
                  __Pausable_init_unchained();
              }
              function __Pausable_init_unchained() internal onlyInitializing {
                  _paused = false;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  _requireNotPaused();
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  _requirePaused();
                  _;
              }
              /**
               * @dev Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Throws if the contract is paused.
               */
              function _requireNotPaused() internal view virtual {
                  require(!paused(), "Pausable: paused");
              }
              /**
               * @dev Throws if the contract is not paused.
               */
              function _requirePaused() internal view virtual {
                  require(paused(), "Pausable: not paused");
              }
              /**
               * @dev Triggers stopped state.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              function _pause() internal virtual whenNotPaused {
                  _paused = true;
                  emit Paused(_msgSender());
              }
              /**
               * @dev Returns to normal state.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              function _unpause() internal virtual whenPaused {
                  _paused = false;
                  emit Unpaused(_msgSender());
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (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 This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[49] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
          pragma solidity ^0.8.1;
          /**
           * @dev Collection of functions related to the address type
           */
          library AddressUpgradeable {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               *
               * [IMPORTANT]
               * ====
               * You shouldn't rely on `isContract` to protect against flash loan attacks!
               *
               * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
               * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
               * constructor.
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize/address.code.length, which returns 0
                  // for contracts in construction, since the code is only stored at the end
                  // of the constructor execution.
                  return account.code.length > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResultFromTarget(target, success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
               * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
               *
               * _Available since v4.8._
               */
              function verifyCallResultFromTarget(
                  address target,
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  if (success) {
                      if (returndata.length == 0) {
                          // only check isContract if the call was successful and the return data is empty
                          // otherwise we already know that it was a contract
                          require(isContract(target), "Address: call to non-contract");
                      }
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              /**
               * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason or using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      _revert(returndata, errorMessage);
                  }
              }
              function _revert(bytes memory returndata, string memory errorMessage) private pure {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      /// @solidity memory-safe-assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          import "../proxy/utils/Initializable.sol";
          /**
           * @dev Provides information about the current execution context, including the
           * sender of the transaction and its data. While these are generally available
           * via msg.sender and msg.data, they should not be accessed in such a direct
           * manner, since when dealing with meta-transactions the account sending and
           * paying for execution may not be the actual sender (as far as an application
           * is concerned).
           *
           * This contract is only required for intermediate, library-like contracts.
           */
          abstract contract ContextUpgradeable is Initializable {
              function __Context_init() internal onlyInitializing {
              }
              function __Context_init_unchained() internal onlyInitializing {
              }
              function _msgSender() internal view virtual returns (address) {
                  return msg.sender;
              }
              function _msgData() internal view virtual returns (bytes calldata) {
                  return msg.data;
              }
              /**
               * @dev This empty reserved space is put in place to allow future versions to add new
               * variables without shifting down storage in the inheritance chain.
               * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
               */
              uint256[50] private __gap;
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
          pragma solidity ^0.8.0;
          import "../StringsUpgradeable.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 ECDSAUpgradeable {
              enum RecoverError {
                  NoError,
                  InvalidSignature,
                  InvalidSignatureLength,
                  InvalidSignatureS,
                  InvalidSignatureV // Deprecated in v4.8
              }
              function _throwError(RecoverError error) private pure {
                  if (error == RecoverError.NoError) {
                      return; // no error: do nothing
                  } else if (error == RecoverError.InvalidSignature) {
                      revert("ECDSA: invalid signature");
                  } else if (error == RecoverError.InvalidSignatureLength) {
                      revert("ECDSA: invalid signature length");
                  } else if (error == RecoverError.InvalidSignatureS) {
                      revert("ECDSA: invalid signature 's' value");
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature` or error string. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               *
               * Documentation for signature generation:
               * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
               * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
               *
               * _Available since v4.3._
               */
              function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
                  if (signature.length == 65) {
                      bytes32 r;
                      bytes32 s;
                      uint8 v;
                      // ecrecover takes the signature parameters, and the only way to get them
                      // currently is to use assembly.
                      /// @solidity memory-safe-assembly
                      assembly {
                          r := mload(add(signature, 0x20))
                          s := mload(add(signature, 0x40))
                          v := byte(0, mload(add(signature, 0x60)))
                      }
                      return tryRecover(hash, v, r, s);
                  } else {
                      return (address(0), RecoverError.InvalidSignatureLength);
                  }
              }
              /**
               * @dev Returns the address that signed a hashed message (`hash`) with
               * `signature`. This address can then be used for verification purposes.
               *
               * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
               * this function rejects them by requiring the `s` value to be in the lower
               * half order, and the `v` value to be either 27 or 28.
               *
               * IMPORTANT: `hash` _must_ be the result of a hash operation for the
               * verification to be secure: it is possible to craft signatures that
               * recover to arbitrary addresses for non-hashed data. A safe way to ensure
               * this is by receiving a hash of the original message (which may otherwise
               * be too long), and then calling {toEthSignedMessageHash} on it.
               */
              function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, signature);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
               *
               * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address, RecoverError) {
                  bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                  uint8 v = uint8((uint256(vs) >> 255) + 27);
                  return tryRecover(hash, v, r, s);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
               *
               * _Available since v4.2._
               */
              function recover(
                  bytes32 hash,
                  bytes32 r,
                  bytes32 vs
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, r, vs);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
               * `r` and `s` signature fields separately.
               *
               * _Available since v4.3._
               */
              function tryRecover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address, RecoverError) {
                  // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                  // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                  // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                  // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                  //
                  // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                  // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                  // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                  // these malleable signatures as well.
                  if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                      return (address(0), RecoverError.InvalidSignatureS);
                  }
                  // If the signature is valid (and not malleable), return the signer address
                  address signer = ecrecover(hash, v, r, s);
                  if (signer == address(0)) {
                      return (address(0), RecoverError.InvalidSignature);
                  }
                  return (signer, RecoverError.NoError);
              }
              /**
               * @dev Overload of {ECDSA-recover} that receives the `v`,
               * `r` and `s` signature fields separately.
               */
              function recover(
                  bytes32 hash,
                  uint8 v,
                  bytes32 r,
                  bytes32 s
              ) internal pure returns (address) {
                  (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
                  _throwError(error);
                  return recovered;
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from a `hash`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                  // 32 is the length in bytes of hash,
                  // enforced by the type signature above
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          32", hash));
              }
              /**
               * @dev Returns an Ethereum Signed Message, created from `s`. This
               * produces hash corresponding to the one signed with the
               * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
               * JSON-RPC method as part of EIP-191.
               *
               * See {recover}.
               */
              function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
          ", StringsUpgradeable.toString(s.length), s));
              }
              /**
               * @dev Returns an Ethereum Signed Typed Data, created from a
               * `domainSeparator` and a `structHash`. This produces hash corresponding
               * to the one signed with the
               * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
               * JSON-RPC method as part of EIP-712.
               *
               * See {recover}.
               */
              function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
                  return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC165 standard, as defined in the
           * https://eips.ethereum.org/EIPS/eip-165[EIP].
           *
           * Implementers can declare support of contract interfaces, which can then be
           * queried by others ({ERC165Checker}).
           *
           * For an implementation, see {ERC165}.
           */
          interface IERC165Upgradeable {
              /**
               * @dev Returns true if this contract implements the interface defined by
               * `interfaceId`. See the corresponding
               * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
               * to learn more about how these ids are created.
               *
               * This function call must use less than 30 000 gas.
               */
              function supportsInterface(bytes4 interfaceId) external view returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Standard math utilities missing in the Solidity language.
           */
          library MathUpgradeable {
              enum Rounding {
                  Down, // Toward negative infinity
                  Up, // Toward infinity
                  Zero // Toward zero
              }
              /**
               * @dev Returns the largest of two numbers.
               */
              function max(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a > b ? a : b;
              }
              /**
               * @dev Returns the smallest of two numbers.
               */
              function min(uint256 a, uint256 b) internal pure returns (uint256) {
                  return a < b ? a : b;
              }
              /**
               * @dev Returns the average of two numbers. The result is rounded towards
               * zero.
               */
              function average(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b) / 2 can overflow.
                  return (a & b) + (a ^ b) / 2;
              }
              /**
               * @dev Returns the ceiling of the division of two numbers.
               *
               * This differs from standard division with `/` in that it rounds up instead
               * of rounding down.
               */
              function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                  // (a + b - 1) / b can overflow on addition, so we distribute.
                  return a == 0 ? 0 : (a - 1) / b + 1;
              }
              /**
               * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
               * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
               * with further edits by Uniswap Labs also under MIT license.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator
              ) internal pure returns (uint256 result) {
                  unchecked {
                      // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                      // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                      // variables such that product = prod1 * 2^256 + prod0.
                      uint256 prod0; // Least significant 256 bits of the product
                      uint256 prod1; // Most significant 256 bits of the product
                      assembly {
                          let mm := mulmod(x, y, not(0))
                          prod0 := mul(x, y)
                          prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                      }
                      // Handle non-overflow cases, 256 by 256 division.
                      if (prod1 == 0) {
                          return prod0 / denominator;
                      }
                      // Make sure the result is less than 2^256. Also prevents denominator == 0.
                      require(denominator > prod1);
                      ///////////////////////////////////////////////
                      // 512 by 256 division.
                      ///////////////////////////////////////////////
                      // Make division exact by subtracting the remainder from [prod1 prod0].
                      uint256 remainder;
                      assembly {
                          // Compute remainder using mulmod.
                          remainder := mulmod(x, y, denominator)
                          // Subtract 256 bit number from 512 bit number.
                          prod1 := sub(prod1, gt(remainder, prod0))
                          prod0 := sub(prod0, remainder)
                      }
                      // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                      // See https://cs.stackexchange.com/q/138556/92363.
                      // Does not overflow because the denominator cannot be zero at this stage in the function.
                      uint256 twos = denominator & (~denominator + 1);
                      assembly {
                          // Divide denominator by twos.
                          denominator := div(denominator, twos)
                          // Divide [prod1 prod0] by twos.
                          prod0 := div(prod0, twos)
                          // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                          twos := add(div(sub(0, twos), twos), 1)
                      }
                      // Shift in bits from prod1 into prod0.
                      prod0 |= prod1 * twos;
                      // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                      // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                      // four bits. That is, denominator * inv = 1 mod 2^4.
                      uint256 inverse = (3 * denominator) ^ 2;
                      // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                      // in modular arithmetic, doubling the correct bits in each step.
                      inverse *= 2 - denominator * inverse; // inverse mod 2^8
                      inverse *= 2 - denominator * inverse; // inverse mod 2^16
                      inverse *= 2 - denominator * inverse; // inverse mod 2^32
                      inverse *= 2 - denominator * inverse; // inverse mod 2^64
                      inverse *= 2 - denominator * inverse; // inverse mod 2^128
                      inverse *= 2 - denominator * inverse; // inverse mod 2^256
                      // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                      // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                      // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                      // is no longer required.
                      result = prod0 * inverse;
                      return result;
                  }
              }
              /**
               * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
               */
              function mulDiv(
                  uint256 x,
                  uint256 y,
                  uint256 denominator,
                  Rounding rounding
              ) internal pure returns (uint256) {
                  uint256 result = mulDiv(x, y, denominator);
                  if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                      result += 1;
                  }
                  return result;
              }
              /**
               * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
               *
               * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
               */
              function sqrt(uint256 a) internal pure returns (uint256) {
                  if (a == 0) {
                      return 0;
                  }
                  // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                  //
                  // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                  // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                  //
                  // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                  // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                  // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                  //
                  // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                  uint256 result = 1 << (log2(a) >> 1);
                  // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                  // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                  // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                  // into the expected uint128 result.
                  unchecked {
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      result = (result + a / result) >> 1;
                      return min(result, a / result);
                  }
              }
              /**
               * @notice Calculates sqrt(a), following the selected rounding direction.
               */
              function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = sqrt(a);
                      return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 2, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 128;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 64;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 32;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 16;
                      }
                      if (value >> 8 > 0) {
                          value >>= 8;
                          result += 8;
                      }
                      if (value >> 4 > 0) {
                          value >>= 4;
                          result += 4;
                      }
                      if (value >> 2 > 0) {
                          value >>= 2;
                          result += 2;
                      }
                      if (value >> 1 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log2(value);
                      return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 10, rounded down, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >= 10**64) {
                          value /= 10**64;
                          result += 64;
                      }
                      if (value >= 10**32) {
                          value /= 10**32;
                          result += 32;
                      }
                      if (value >= 10**16) {
                          value /= 10**16;
                          result += 16;
                      }
                      if (value >= 10**8) {
                          value /= 10**8;
                          result += 8;
                      }
                      if (value >= 10**4) {
                          value /= 10**4;
                          result += 4;
                      }
                      if (value >= 10**2) {
                          value /= 10**2;
                          result += 2;
                      }
                      if (value >= 10**1) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log10(value);
                      return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
                  }
              }
              /**
               * @dev Return the log in base 256, rounded down, of a positive value.
               * Returns 0 if given 0.
               *
               * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
               */
              function log256(uint256 value) internal pure returns (uint256) {
                  uint256 result = 0;
                  unchecked {
                      if (value >> 128 > 0) {
                          value >>= 128;
                          result += 16;
                      }
                      if (value >> 64 > 0) {
                          value >>= 64;
                          result += 8;
                      }
                      if (value >> 32 > 0) {
                          value >>= 32;
                          result += 4;
                      }
                      if (value >> 16 > 0) {
                          value >>= 16;
                          result += 2;
                      }
                      if (value >> 8 > 0) {
                          result += 1;
                      }
                  }
                  return result;
              }
              /**
               * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
               * Returns 0 if given 0.
               */
              function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                  unchecked {
                      uint256 result = log256(value);
                      return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
          pragma solidity ^0.8.0;
          import "./math/MathUpgradeable.sol";
          /**
           * @dev String operations.
           */
          library StringsUpgradeable {
              bytes16 private constant _SYMBOLS = "0123456789abcdef";
              uint8 private constant _ADDRESS_LENGTH = 20;
              /**
               * @dev Converts a `uint256` to its ASCII `string` decimal representation.
               */
              function toString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      uint256 length = MathUpgradeable.log10(value) + 1;
                      string memory buffer = new string(length);
                      uint256 ptr;
                      /// @solidity memory-safe-assembly
                      assembly {
                          ptr := add(buffer, add(32, length))
                      }
                      while (true) {
                          ptr--;
                          /// @solidity memory-safe-assembly
                          assembly {
                              mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                          }
                          value /= 10;
                          if (value == 0) break;
                      }
                      return buffer;
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
               */
              function toHexString(uint256 value) internal pure returns (string memory) {
                  unchecked {
                      return toHexString(value, MathUpgradeable.log256(value) + 1);
                  }
              }
              /**
               * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
               */
              function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                  bytes memory buffer = new bytes(2 * length + 2);
                  buffer[0] = "0";
                  buffer[1] = "x";
                  for (uint256 i = 2 * length + 1; i > 1; --i) {
                      buffer[i] = _SYMBOLS[value & 0xf];
                      value >>= 4;
                  }
                  require(value == 0, "Strings: hex length insufficient");
                  return string(buffer);
              }
              /**
               * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
               */
              function toHexString(address addr) internal pure returns (string memory) {
                  return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {HeyMintERC721AUpgradeable} from "./HeyMintERC721AUpgradeable.sol";
          import {BaseConfig, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          import {ERC721AUpgradeable, IERC721AUpgradeable} from "erc721a-upgradeable/contracts/ERC721AUpgradeable.sol";
          import {ECDSAUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
          import {IERC2981Upgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
          contract HeyMintERC721ABase is HeyMintERC721AUpgradeable, IERC2981Upgradeable {
              using HeyMintStorage for HeyMintStorage.State;
              using ECDSAUpgradeable for bytes32;
              // Default subscription address to use to enable royalty enforcement on certain exchanges like OpenSea
              address public constant CORI_SUBSCRIPTION_ADDRESS =
                  0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;
              // Default subscription address to use as a placeholder for no royalty enforcement
              address public constant EMPTY_SUBSCRIPTION_ADDRESS =
                  0x511af84166215d528ABf8bA6437ec4BEcF31934B;
              /**
               * @notice Initializes a new child deposit contract
               * @param _name The name of the token
               * @param _symbol The symbol of the token
               * @param _config Base configuration settings
               */
              function initialize(
                  string memory _name,
                  string memory _symbol,
                  BaseConfig memory _config
              ) public initializerERC721A initializer {
                  __ERC721A_init(_name, _symbol);
                  __Ownable_init();
                  __ReentrancyGuard_init();
                  __OperatorFilterer_init(
                      _config.enforceRoyalties == true
                          ? CORI_SUBSCRIPTION_ADDRESS
                          : EMPTY_SUBSCRIPTION_ADDRESS,
                      true
                  );
                  HeyMintStorage.state().cfg = _config;
                  // If public sale start time is set but end time is not, set default end time
                  if (_config.publicSaleStartTime > 0 && _config.publicSaleEndTime == 0) {
                      HeyMintStorage.state().cfg.publicSaleEndTime =
                          _config.publicSaleStartTime +
                          520 weeks;
                  }
                  // If public sale end time is set but not start time, set default start time
                  if (_config.publicSaleEndTime > 0 && _config.publicSaleStartTime == 0) {
                      HeyMintStorage.state().cfg.publicSaleStartTime = uint32(
                          block.timestamp
                      );
                  }
                  // If presale start time is set but end time is not, set default end time
                  if (_config.presaleStartTime > 0 && _config.presaleEndTime == 0) {
                      HeyMintStorage.state().cfg.presaleEndTime =
                          _config.presaleStartTime +
                          520 weeks;
                  }
                  // If presale end time is set but not start time, set default start time
                  if (_config.presaleEndTime > 0 && _config.presaleStartTime == 0) {
                      HeyMintStorage.state().cfg.presaleStartTime = uint32(
                          block.timestamp
                      );
                  }
              }
              // ============ BASE FUNCTIONALITY ============
              function pause() external onlyOwner {
                  _pause();
              }
              function unpause() external onlyOwner {
                  _unpause();
              }
              /**
               * @notice Returns true if the contract implements the interface defined by interfaceId
               * @param interfaceId The interface identifier, as specified in ERC-165
               */
              function supportsInterface(
                  bytes4 interfaceId
              )
                  public
                  view
                  virtual
                  override(HeyMintERC721AUpgradeable, IERC165Upgradeable)
                  returns (bool)
              {
                  return HeyMintERC721AUpgradeable.supportsInterface(interfaceId);
              }
              /**
               * @notice Reduce the max supply of tokens
               * @param _newMaxSupply The new maximum supply of tokens available to mint
               */
              function reduceMaxSupply(uint16 _newMaxSupply) external onlyOwner {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  require(_newMaxSupply < cfg.maxSupply, "NEW_MAX_SUPPLY_TOO_HIGH");
                  require(
                      _newMaxSupply >= totalSupply(),
                      "SUPPLY_LOWER_THAN_MINTED_TOKENS"
                  );
                  cfg.maxSupply = _newMaxSupply;
              }
              // ============ METADATA ============
              /**
               * @notice Returns the base URI for all tokens. If the base URI is not set, it will be generated based on the project ID
               */
              function _baseURI() internal view virtual override returns (string memory) {
                  return HeyMintStorage.state().cfg.uriBase;
              }
              /**
               * @notice Overrides the default ERC721 tokenURI function to look for specific token URIs when present
               * @param tokenId The token ID to query
               */
              function tokenURI(
                  uint256 tokenId
              )
                  public
                  view
                  virtual
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  returns (string memory)
              {
                  if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  string memory specificTokenURI = state.data.tokenURIs[tokenId];
                  if (bytes(specificTokenURI).length != 0) return specificTokenURI;
                  string memory baseURI = _baseURI();
                  if (bytes(baseURI).length == 0) return "";
                  uint256 burnTokenId = state.data.tokenIdToBurnTokenId[tokenId];
                  uint256 tokenURITokenId = state.advCfg.useBurnTokenIdForMetadata &&
                      burnTokenId != 0
                      ? burnTokenId
                      : tokenId;
                  return string(abi.encodePacked(baseURI, _toString(tokenURITokenId)));
              }
              /**
               * @notice Update the base token URI
               * @param _newBaseURI The new base URI to use
               */
              function setBaseURI(string calldata _newBaseURI) external onlyOwner {
                  require(!HeyMintStorage.state().advCfg.metadataFrozen, "NOT_ACTIVE");
                  HeyMintStorage.state().cfg.uriBase = _newBaseURI;
              }
              /**
               * @notice Freeze metadata so it can never be changed again
               */
              function freezeMetadata() external onlyOwner {
                  HeyMintStorage.state().advCfg.metadataFrozen = true;
              }
              // ============ ERC-2981 ROYALTY ============
              /**
               * @notice Basic gas saving implementation of ERC-2981 royaltyInfo function with receiver set to the contract owner
               * @param _salePrice The sale price used to determine the royalty amount
               */
              function royaltyInfo(
                  uint256,
                  uint256 _salePrice
              ) external view override returns (address, uint256) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  address payoutAddress = state.advCfg.royaltyPayoutAddress !=
                      address(0x0)
                      ? state.advCfg.royaltyPayoutAddress
                      : owner();
                  if (payoutAddress == address(0x0)) {
                      return (payoutAddress, 0);
                  }
                  return (payoutAddress, (_salePrice * state.cfg.royaltyBps) / 10000);
              }
              // ============ PAYOUT ============
              /**
               * @notice Withdraws all funds held within contract
               */
              function withdraw() external nonReentrant onlyOwner {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (state.cfg.fundingEndsAt > 0) {
                      require(
                          state.data.fundingTargetReached,
                          "FUNDING_TARGET_NOT_REACHED"
                      );
                  }
                  if (state.advCfg.refundEndsAt > 0) {
                      require(!refundGuaranteeActive(), "REFUND_GUARANTEE_STILL_ACTIVE");
                  }
                  uint256 balance = address(this).balance;
                  if (state.advCfg.payoutAddresses.length == 0) {
                      (bool success, ) = payable(owner()).call{value: balance}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      for (uint256 i = 0; i < state.advCfg.payoutAddresses.length; i++) {
                          uint256 amount = (balance * state.advCfg.payoutBasisPoints[i]) /
                              10000;
                          (bool success, ) = HeyMintStorage
                              .state()
                              .advCfg
                              .payoutAddresses[i]
                              .call{value: amount}("");
                          require(success, "TRANSFER_FAILED");
                      }
                  }
              }
              // ============ PUBLIC SALE ============
              /**
               * @notice Returns the public price in wei. Public price is stored with 5 decimals (1 = 0.00001 ETH), so total 5 + 13 == 18 decimals
               */
              function publicPriceInWei() public view returns (uint256) {
                  return uint256(HeyMintStorage.state().cfg.publicPrice) * 10 ** 13;
              }
              /**
               * @notice To be updated by contract owner to allow public sale minting
               */
              function setPublicSaleState(bool _saleActiveState) external onlyOwner {
                  HeyMintStorage.state().cfg.publicSaleActive = _saleActiveState;
              }
              /**
               * @notice Update the public mint price
               * @param _publicPrice The new public mint price to use
               */
              function setPublicPrice(uint32 _publicPrice) external onlyOwner {
                  HeyMintStorage.state().cfg.publicPrice = _publicPrice;
              }
              /**
               * @notice Set the maximum mints allowed per a given address in the public sale
               * @param _mintsAllowed The new maximum mints allowed per address
               */
              function setPublicMintsAllowedPerAddress(
                  uint8 _mintsAllowed
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.publicMintsAllowedPerAddress = _mintsAllowed;
              }
              /**
               * @notice Set the maximum mints allowed per a given transaction in the public sale
               * @param _mintsAllowed The new maximum mints allowed per transaction
               */
              function setPublicMintsAllowedPerTransaction(
                  uint8 _mintsAllowed
              ) external onlyOwner {
                  HeyMintStorage
                      .state()
                      .cfg
                      .publicMintsAllowedPerTransaction = _mintsAllowed;
              }
              /**
               * @notice Update the start time for public mint
               * @param _publicSaleStartTime The new start time for public mint
               */
              function setPublicSaleStartTime(
                  uint32 _publicSaleStartTime
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.publicSaleStartTime = _publicSaleStartTime;
              }
              /**
               * @notice Update the end time for public mint
               * @param _publicSaleEndTime The new end time for public mint
               */
              function setPublicSaleEndTime(
                  uint32 _publicSaleEndTime
              ) external onlyOwner {
                  require(_publicSaleEndTime > block.timestamp, "TIME_IN_PAST");
                  HeyMintStorage.state().cfg.publicSaleEndTime = _publicSaleEndTime;
              }
              /**
               * @notice Update whether or not to use the automatic public sale times
               * @param _usePublicSaleTimes Whether or not to use the automatic public sale times
               */
              function setUsePublicSaleTimes(
                  bool _usePublicSaleTimes
              ) external onlyOwner {
                  HeyMintStorage.state().cfg.usePublicSaleTimes = _usePublicSaleTimes;
              }
              /**
               * @notice Returns if public sale times are active. If required config settings are not set, returns true.
               */
              function publicSaleTimeIsActive() public view returns (bool) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  if (
                      state.cfg.usePublicSaleTimes == false ||
                      state.cfg.publicSaleStartTime == 0 ||
                      state.cfg.publicSaleEndTime == 0
                  ) {
                      return true;
                  }
                  return
                      block.timestamp >= state.cfg.publicSaleStartTime &&
                      block.timestamp <= state.cfg.publicSaleEndTime;
              }
              /**
               * @notice Allow for public minting of tokens
               * @param _numTokens The number of tokens to mint
               */
              function publicMint(uint256 _numTokens) external payable nonReentrant {
                  BaseConfig storage cfg = HeyMintStorage.state().cfg;
                  require(cfg.publicSaleActive, "NOT_ACTIVE");
                  require(publicSaleTimeIsActive(), "NOT_ACTIVE");
                  require(
                      cfg.publicMintsAllowedPerAddress == 0 ||
                          _numberMinted(msg.sender) + _numTokens <=
                          cfg.publicMintsAllowedPerAddress,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      cfg.publicMintsAllowedPerTransaction == 0 ||
                          _numTokens <= cfg.publicMintsAllowedPerTransaction,
                      "MAX_MINTS_EXCEEDED"
                  );
                  require(
                      totalSupply() + _numTokens <= cfg.maxSupply,
                      "MAX_SUPPLY_EXCEEDED"
                  );
                  uint256 publicPrice = publicPriceInWei();
                  if (cfg.heyMintFeeActive) {
                      uint256 heymintFee = _numTokens * heymintFeePerToken();
                      require(
                          msg.value == publicPrice * _numTokens + heymintFee,
                          "INVALID_PRICE_PAID"
                      );
                      (bool success, ) = heymintPayoutAddress.call{value: heymintFee}("");
                      require(success, "TRANSFER_FAILED");
                  } else {
                      require(
                          msg.value == publicPrice * _numTokens,
                          "INVALID_PRICE_PAID"
                      );
                  }
                  if (cfg.fundingEndsAt > 0) {
                      uint256 firstTokenIdToMint = _nextTokenId();
                      for (uint256 i = 0; i < _numTokens; i++) {
                          HeyMintStorage.state().data.pricePaid[
                              firstTokenIdToMint + i
                          ] = publicPrice;
                      }
                  }
                  _safeMint(msg.sender, _numTokens);
              }
              // ============ REFUND ============
              /**
               * Will return true if token holders can still return their tokens for a refund
               */
              function refundGuaranteeActive() public view returns (bool) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  return block.timestamp < state.advCfg.refundEndsAt;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          import {Data, HeyMintStorage} from "../libraries/HeyMintStorage.sol";
          import {ERC721AUpgradeable, IERC721AUpgradeable, ERC721AStorage} from "erc721a-upgradeable/contracts/ERC721AUpgradeable.sol";
          import {ERC4907AUpgradeable} from "erc721a-upgradeable/contracts/extensions/ERC4907AUpgradeable.sol";
          import {ERC721AQueryableUpgradeable} from "erc721a-upgradeable/contracts/extensions/ERC721AQueryableUpgradeable.sol";
          import {IERC2981Upgradeable, IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
          import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
          import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
          import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
          import {RevokableOperatorFiltererUpgradeable} from "operator-filter-registry/src/upgradeable/RevokableOperatorFiltererUpgradeable.sol";
          /**
           * @title HeyMintERC721AUpgradeable
           * @author HeyMint Launchpad (https://join.heymint.xyz)
           * @notice This contract contains shared logic to be inherited by all implementation contracts
           */
          contract HeyMintERC721AUpgradeable is
              ERC4907AUpgradeable,
              ERC721AQueryableUpgradeable,
              OwnableUpgradeable,
              PausableUpgradeable,
              ReentrancyGuardUpgradeable,
              RevokableOperatorFiltererUpgradeable
          {
              using HeyMintStorage for HeyMintStorage.State;
              uint256 public constant defaultHeymintFeePerToken = 0.0007 ether;
              address public constant heymintPayoutAddress =
                  0xE1FaC470dE8dE91c66778eaa155C64c7ceEFc851;
              // ============ BASE FUNCTIONALITY ============
              /**
               * @dev Overrides the default ERC721A _startTokenId() so tokens begin at 1 instead of 0
               */
              function _startTokenId() internal view virtual override returns (uint256) {
                  return 1;
              }
              /**
               * @notice Wraps and exposes publicly _numberMinted() from ERC721A
               * @param _owner The address of the owner to check
               */
              function numberMinted(address _owner) public view returns (uint256) {
                  return _numberMinted(_owner);
              }
              /**
               * @dev Used to directly approve a token for transfers by the current msg.sender,
               * bypassing the typical checks around msg.sender being the owner of a given token.
               * This approval will be automatically deleted once the token is transferred.
               * @param _tokenId The ID of the token to approve
               */
              function _directApproveMsgSenderFor(uint256 _tokenId) internal {
                  ERC721AStorage.layout()._tokenApprovals[_tokenId].value = msg.sender;
              }
              /**
               * @notice Returns the owner of the contract
               */
              function owner()
                  public
                  view
                  virtual
                  override(OwnableUpgradeable, RevokableOperatorFiltererUpgradeable)
                  returns (address)
              {
                  return OwnableUpgradeable.owner();
              }
              // https://chiru-labs.github.io/ERC721A/#/migration?id=supportsinterface
              /**
               * @notice Returns true if the contract implements the interface defined by interfaceId
               * @param interfaceId The interface identifier, as specified in ERC-165
               */
              function supportsInterface(
                  bytes4 interfaceId
              )
                  public
                  view
                  virtual
                  override(ERC721AUpgradeable, IERC721AUpgradeable, ERC4907AUpgradeable)
                  returns (bool)
              {
                  // Supports the following interfaceIds:
                  // - IERC165: 0x01ffc9a7
                  // - IERC721: 0x80ac58cd
                  // - IERC721Metadata: 0x5b5e139f
                  // - IERC2981: 0x2a55205a
                  // - IERC4907: 0xad092b5c
                  return
                      ERC721AUpgradeable.supportsInterface(interfaceId) ||
                      type(IERC2981Upgradeable).interfaceId == interfaceId ||
                      ERC4907AUpgradeable.supportsInterface(interfaceId);
              }
              // ============ HEYMINT FEE ============
              /**
               * @notice Returns the HeyMint fee per token. If the fee is 0, the default fee is returned
               */
              function heymintFeePerToken() public view returns (uint256) {
                  uint256 fee = HeyMintStorage.state().data.heymintFeePerToken;
                  return fee == 0 ? defaultHeymintFeePerToken : fee;
              }
              // ============ OPERATOR FILTER REGISTRY ============
              /**
               * @notice Override default ERC-721 setApprovalForAll to require that the operator is not from a blocklisted exchange
               * @dev See {IERC721-setApprovalForAll}.
               * @param operator Address to add to the set of authorized operators
               * @param approved True if the operator is approved, false to revoke approval
               */
              function setApprovalForAll(
                  address operator,
                  bool approved
              )
                  public
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperatorApproval(operator)
              {
                  require(
                      !HeyMintStorage.state().cfg.soulbindingActive,
                      "TOKEN_IS_SOULBOUND"
                  );
                  super.setApprovalForAll(operator, approved);
              }
              /**
               * @notice Override default ERC721 approve to require that the operator is not from a blocklisted exchange
               * @dev See {IERC721-approve}.
               * @param to Address to receive the approval
               * @param tokenId ID of the token to be approved
               */
              function approve(
                  address to,
                  uint256 tokenId
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperatorApproval(to)
              {
                  require(
                      !HeyMintStorage.state().cfg.soulbindingActive,
                      "TOKEN_IS_SOULBOUND"
                  );
                  super.approve(to, tokenId);
              }
              /**
               * @dev See {IERC721-transferFrom}.
               *      The added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
               */
              function transferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperator(from)
              {
                  super.transferFrom(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               *      The added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperator(from)
              {
                  super.safeTransferFrom(from, to, tokenId);
              }
              /**
               * @dev See {IERC721-safeTransferFrom}.
               *      The added modifier ensures that the operator is allowed by the OperatorFilterRegistry.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId,
                  bytes memory data
              )
                  public
                  payable
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  onlyAllowedOperator(from)
              {
                  super.safeTransferFrom(from, to, tokenId, data);
              }
              // ============ RANDOM HASH ============
              /**
               * @notice Generate a suitably random hash from block data
               * Can be used later to determine any sort of arbitrary outcome
               * @param _tokenId The token ID to generate a random hash for
               */
              function _generateRandomHash(uint256 _tokenId) internal {
                  Data storage data = HeyMintStorage.state().data;
                  if (data.randomHashStore[_tokenId] == bytes32(0)) {
                      data.randomHashStore[_tokenId] = keccak256(
                          abi.encode(block.prevrandao, _tokenId)
                      );
                  }
              }
              // ============ TOKEN TRANSFER CHECKS ============
              function _beforeTokenTransfers(
                  address from,
                  address to,
                  uint256 tokenId,
                  uint256 quantity
              ) internal override whenNotPaused onlyAllowedOperator(from) {
                  HeyMintStorage.State storage state = HeyMintStorage.state();
                  require(
                      !state.advCfg.stakingActive ||
                          state.data.stakingTransferActive ||
                          state.data.currentTimeStaked[tokenId] == 0,
                      "TOKEN_IS_STAKED"
                  );
                  require(
                      state.data.tokenOwnersOnLoan[tokenId] == address(0),
                      "CANNOT_TRANSFER_LOANED_TOKEN"
                  );
                  if (
                      state.cfg.soulbindingActive &&
                      !state.data.soulboundAdminTransferInProgress
                  ) {
                      require(from == address(0), "TOKEN_IS_SOULBOUND");
                  }
                  if (state.cfg.randomHashActive && from == address(0)) {
                      _generateRandomHash(tokenId);
                  }
                  super._beforeTokenTransfers(from, to, tokenId, quantity);
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity 0.8.18;
          struct BaseConfig {
              // If true tokens can be minted in the public sale
              bool publicSaleActive;
              // If enabled, automatic start and stop times for the public sale will be enforced, otherwise ignored
              bool usePublicSaleTimes;
              // If true tokens can be minted in the presale
              bool presaleActive;
              // If enabled, automatic start and stop times for the presale will be enforced, otherwise ignored
              bool usePresaleTimes;
              // If true, all tokens will be soulbound
              bool soulbindingActive;
              // If true, a random hash will be generated for each token
              bool randomHashActive;
              // If true, the default CORI subscription address will be used to enforce royalties with the Operator Filter Registry
              bool enforceRoyalties;
              // If true, HeyMint fees will be charged for minting tokens
              bool heyMintFeeActive;
              // The number of tokens that can be minted in the public sale per address
              uint8 publicMintsAllowedPerAddress;
              // The number of tokens that can be minted in the presale per address
              uint8 presaleMintsAllowedPerAddress;
              // The number of tokens that can be minted in the public sale per transaction
              uint8 publicMintsAllowedPerTransaction;
              // The number of tokens that can be minted in the presale sale per transaction
              uint8 presaleMintsAllowedPerTransaction;
              // Maximum supply of tokens that can be minted
              uint16 maxSupply;
              // Total number of tokens available for minting in the presale
              uint16 presaleMaxSupply;
              // The royalty payout percentage in basis points
              uint16 royaltyBps;
              // The price of a token in the public sale in 1/100,000 ETH - e.g. 1 = 0.00001 ETH, 100,000 = 1 ETH - multiply by 10^13 to get correct wei amount
              uint32 publicPrice;
              // The price of a token in the presale in 1/100,000 ETH
              uint32 presalePrice;
              // Used to create a default HeyMint Launchpad URI for token metadata to save gas over setting a custom URI and increase fetch reliability
              uint24 projectId;
              // The base URI for all token metadata
              string uriBase;
              // The address used to sign and validate presale mints
              address presaleSignerAddress;
              // The automatic start time for the public sale (if usePublicSaleTimes is true and publicSaleActive is true)
              uint32 publicSaleStartTime;
              // The automatic end time for the public sale (if usePublicSaleTimes is true and publicSaleActive is true)
              uint32 publicSaleEndTime;
              // The automatic start time for the presale (if usePresaleTimes is true and presaleActive is true)
              uint32 presaleStartTime;
              // The automatic end time for the presale (if usePresaleTimes is true and presaleActive is true)
              uint32 presaleEndTime;
              // If set, the UTC timestamp in seconds by which the fundingTarget must be met or funds are refundable
              uint32 fundingEndsAt;
              // The amount of centiETH that must be raised by fundingEndsAt or funds are refundable - multiply by 10^16
              uint32 fundingTarget;
          }
          struct AdvancedConfig {
              // When false, tokens cannot be staked but can still be unstaked
              bool stakingActive;
              // When false, tokens cannot be loaned but can still be retrieved
              bool loaningActive;
              // If true tokens can be claimed for free
              bool freeClaimActive;
              // The number of tokens that can be minted per free claim
              uint8 mintsPerFreeClaim;
              // Optional address of an NFT that is eligible for free claim
              address freeClaimContractAddress;
              // If true tokens can be burned in order to mint
              bool burnClaimActive;
              // If true, the original token id of a burned token will be used for metadata
              bool useBurnTokenIdForMetadata;
              // The number of tokens that can be minted per burn transaction
              uint8 mintsPerBurn;
              // The payment required alongside a burn transaction in order to mint in 1/100,000 ETH
              uint32 burnPayment;
              // Permanently freezes payout addresses and basis points so they can never be updated
              bool payoutAddressesFrozen;
              // If set, the UTC timestamp in seconds until which tokens are refundable for refundPrice
              uint32 refundEndsAt;
              // The amount returned to a user in a token refund in 1/100,000 ETH
              uint32 refundPrice;
              // Permanently freezes metadata so it can never be changed
              bool metadataFrozen;
              // If true the soulbind admin address is permanently disabled
              bool soulbindAdminTransfersPermanentlyDisabled;
              // If true deposit tokens can be burned in order to mint
              bool depositClaimActive;
              // If additional payment is required to mint, this is the amount required in centiETH
              uint32 remainingDepositPayment;
              // The deposit token smart contract address
              address depositContractAddress;
              // The merkle root used to validate if deposit tokens are eligible to burn to mint
              bytes32 depositMerkleRoot;
              // The respective share of funds to be sent to each address in payoutAddresses in basis points
              uint16[] payoutBasisPoints;
              // The addresses to which funds are sent when a token is sold. If empty, funds are sent to the contract owner.
              address[] payoutAddresses;
              // Optional address where royalties are paid out. If not set, royalties are paid to the contract owner.
              address royaltyPayoutAddress;
              // Used to allow transferring soulbound tokens with admin privileges. Defaults to the contract owner if not set.
              address soulboundAdminAddress;
              // The address where refunded tokens are returned. If not set, refunded tokens are sent to the contract owner.
              address refundAddress;
              // An address authorized to call the creditCardMint function.
              address creditCardMintAddress;
          }
          struct BurnToken {
              // The contract address of the token to be burned
              address contractAddress;
              // The type of contract - 1 = ERC-721, 2 = ERC-1155
              uint8 tokenType;
              // The number of tokens to burn per mint
              uint8 tokensPerBurn;
              // The ID of the token on an ERC-1155 contract eligible for burn; unused for ERC-721
              uint16 tokenId;
          }
          struct Data {
              // ============ BASE FUNCTIONALITY ============
              // HeyMint fee to be paid per minted token (if not set, defaults to defaultHeymintFeePerToken)
              uint256 heymintFeePerToken;
              // Keeps track of if advanced config settings have been initialized to prevent setting multiple times
              bool advancedConfigInitialized;
              // A mapping of token IDs to specific tokenURIs for tokens that have custom metadata
              mapping(uint256 => string) tokenURIs;
              // ============ CONDITIONAL FUNDING ============
              // If true, the funding target was reached and funds are not refundable
              bool fundingTargetReached;
              // If true, funding success has been determined and determineFundingSuccess() can no longer be called
              bool fundingSuccessDetermined;
              // A mapping of token ID to price paid for the token
              mapping(uint256 => uint256) pricePaid;
              // ============ SOULBINDING ============
              // Used to allow an admin to transfer soulbound tokens when necessary
              bool soulboundAdminTransferInProgress;
              // ============ BURN TO MINT ============
              // Maps a token id to the burn token id that was used to mint it to match metadata
              mapping(uint256 => uint256) tokenIdToBurnTokenId;
              // ============ STAKING ============
              // Used to allow direct transfers of staked tokens without unstaking first
              bool stakingTransferActive;
              // Returns the UNIX timestamp at which a token began staking if currently staked
              mapping(uint256 => uint256) currentTimeStaked;
              // Returns the total time a token has been staked in seconds, not counting the current staking time if any
              mapping(uint256 => uint256) totalTimeStaked;
              // ============ LOANING ============
              // Used to keep track of the total number of tokens on loan
              uint256 currentLoanTotal;
              // Returns the total number of tokens loaned by an address
              mapping(address => uint256) totalLoanedPerAddress;
              // Returns the address of the original token owner if a token is currently on loan
              mapping(uint256 => address) tokenOwnersOnLoan;
              // ============ FREE CLAIM ============
              // If true token has already been used to claim and cannot be used again
              mapping(uint256 => bool) freeClaimUsed;
              // ============ RANDOM HASH ============
              // Stores a random hash for each token ID
              mapping(uint256 => bytes32) randomHashStore;
          }
          library HeyMintStorage {
              struct State {
                  BaseConfig cfg;
                  AdvancedConfig advCfg;
                  BurnToken[] burnTokens;
                  Data data;
              }
              bytes32 internal constant STORAGE_SLOT =
                  keccak256("heymint.launchpad.storage.erc721a");
              function state() internal pure returns (State storage s) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      s.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev This is a base contract to aid in writing upgradeable diamond facet 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.
           *
           * 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.
           */
          import {ERC721A__InitializableStorage} from './ERC721A__InitializableStorage.sol';
          abstract contract ERC721A__Initializable {
              using ERC721A__InitializableStorage for ERC721A__InitializableStorage.Layout;
              /**
               * @dev Modifier to protect an initializer function from being invoked twice.
               */
              modifier initializerERC721A() {
                  // If the contract is initializing we ignore whether _initialized is set in order to support multiple
                  // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
                  // contract may have been reentered.
                  require(
                      ERC721A__InitializableStorage.layout()._initializing
                          ? _isConstructor()
                          : !ERC721A__InitializableStorage.layout()._initialized,
                      'ERC721A__Initializable: contract is already initialized'
                  );
                  bool isTopLevelCall = !ERC721A__InitializableStorage.layout()._initializing;
                  if (isTopLevelCall) {
                      ERC721A__InitializableStorage.layout()._initializing = true;
                      ERC721A__InitializableStorage.layout()._initialized = true;
                  }
                  _;
                  if (isTopLevelCall) {
                      ERC721A__InitializableStorage.layout()._initializing = false;
                  }
              }
              /**
               * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
               * {initializer} modifier, directly or indirectly.
               */
              modifier onlyInitializingERC721A() {
                  require(
                      ERC721A__InitializableStorage.layout()._initializing,
                      'ERC721A__Initializable: contract is not initializing'
                  );
                  _;
              }
              /// @dev Returns true if and only if the function is running in the constructor
              function _isConstructor() private view returns (bool) {
                  // extcodesize checks the size of the code stored in an address, and
                  // address returns the current address. Since the code is still not
                  // deployed when running a constructor, any checks on its code size will
                  // yield zero, making it an effective way to detect if a contract is
                  // under construction or not.
                  address self = address(this);
                  uint256 cs;
                  assembly {
                      cs := extcodesize(self)
                  }
                  return cs == 0;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          /**
           * @dev This is a base storage for the  initialization function for upgradeable diamond facet contracts
           **/
          library ERC721A__InitializableStorage {
              struct Layout {
                  /*
                   * Indicates that the contract has been initialized.
                   */
                  bool _initialized;
                  /*
                   * Indicates that the contract is in the process of being initialized.
                   */
                  bool _initializing;
              }
              bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.initializable.facet');
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          library ERC721AStorage {
              // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
              struct TokenApprovalRef {
                  address value;
              }
              struct Layout {
                  // =============================================================
                  //                            STORAGE
                  // =============================================================
                  // The next token ID to be minted.
                  uint256 _currentIndex;
                  // The number of tokens burned.
                  uint256 _burnCounter;
                  // Token name
                  string _name;
                  // Token symbol
                  string _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) _packedOwnerships;
                  // Mapping owner address to address data.
                  //
                  // Bits Layout:
                  // - [0..63]    `balance`
                  // - [64..127]  `numberMinted`
                  // - [128..191] `numberBurned`
                  // - [192..255] `aux`
                  mapping(address => uint256) _packedAddressData;
                  // Mapping from token ID to approved address.
                  mapping(uint256 => ERC721AStorage.TokenApprovalRef) _tokenApprovals;
                  // Mapping from owner to operator approvals
                  mapping(address => mapping(address => bool)) _operatorApprovals;
              }
              bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC721A');
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721AUpgradeable.sol';
          import {ERC721AStorage} from './ERC721AStorage.sol';
          import './ERC721A__Initializable.sol';
          /**
           * @dev Interface of ERC721 token receiver.
           */
          interface ERC721A__IERC721ReceiverUpgradeable {
              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 ERC721AUpgradeable is ERC721A__Initializable, IERC721AUpgradeable {
              using ERC721AStorage for ERC721AStorage.Layout;
              // =============================================================
              //                           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;
              // =============================================================
              //                          CONSTRUCTOR
              // =============================================================
              function __ERC721A_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
                  __ERC721A_init_unchained(name_, symbol_);
              }
              function __ERC721A_init_unchained(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
                  ERC721AStorage.layout()._name = name_;
                  ERC721AStorage.layout()._symbol = symbol_;
                  ERC721AStorage.layout()._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 ERC721AStorage.layout()._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 ERC721AStorage.layout()._currentIndex - ERC721AStorage.layout()._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 ERC721AStorage.layout()._currentIndex - _startTokenId();
                  }
              }
              /**
               * @dev Returns the total number of tokens burned.
               */
              function _totalBurned() internal view virtual returns (uint256) {
                  return ERC721AStorage.layout()._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 ERC721AStorage.layout()._packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
              }
              /**
               * Returns the number of tokens minted by `owner`.
               */
              function _numberMinted(address owner) internal view returns (uint256) {
                  return
                      (ERC721AStorage.layout()._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
                      (ERC721AStorage.layout()._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(ERC721AStorage.layout()._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 = ERC721AStorage.layout()._packedAddressData[owner];
                  uint256 auxCasted;
                  // Cast `aux` with assembly to avoid redundant masking.
                  assembly {
                      auxCasted := aux
                  }
                  packed = (packed & _BITMASK_AUX_COMPLEMENT) | (auxCasted << _BITPOS_AUX);
                  ERC721AStorage.layout()._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 ERC721AStorage.layout()._name;
              }
              /**
               * @dev Returns the token collection symbol.
               */
              function symbol() public view virtual override returns (string memory) {
                  return ERC721AStorage.layout()._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(ERC721AStorage.layout()._packedOwnerships[index]);
              }
              /**
               * @dev Initializes the ownership slot minted at `index` for efficiency purposes.
               */
              function _initializeOwnershipAt(uint256 index) internal virtual {
                  if (ERC721AStorage.layout()._packedOwnerships[index] == 0) {
                      ERC721AStorage.layout()._packedOwnerships[index] = _packedOwnershipOf(index);
                  }
              }
              /**
               * Returns the packed ownership data of `tokenId`.
               */
              function _packedOwnershipOf(uint256 tokenId) private view returns (uint256 packed) {
                  if (_startTokenId() <= tokenId) {
                      packed = ERC721AStorage.layout()._packedOwnerships[tokenId];
                      // If not burned.
                      if (packed & _BITMASK_BURNED == 0) {
                          // If the data at the starting slot does not exist, start the scan.
                          if (packed == 0) {
                              if (tokenId >= ERC721AStorage.layout()._currentIndex) revert OwnerQueryForNonexistentToken();
                              // 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, `tokenId` will not underflow.
                              //
                              // We can directly compare the packed value.
                              // If the address is zero, packed will be zero.
                              for (;;) {
                                  unchecked {
                                      packed = ERC721AStorage.layout()._packedOwnerships[--tokenId];
                                  }
                                  if (packed == 0) continue;
                                  return packed;
                              }
                          }
                          // Otherwise, the data exists and is not burned. We can skip the scan.
                          // This is possible because we have already achieved the target condition.
                          // This saves 2143 gas on transfers of initialized tokens.
                          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. See {ERC721A-_approve}.
               *
               * Requirements:
               *
               * - The caller must own the token or be an approved operator.
               */
              function approve(address to, uint256 tokenId) public payable virtual override {
                  _approve(to, tokenId, true);
              }
              /**
               * @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 ERC721AStorage.layout()._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 {
                  ERC721AStorage.layout()._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 ERC721AStorage.layout()._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 < ERC721AStorage.layout()._currentIndex && // If within bounds,
                      ERC721AStorage.layout()._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)
              {
                  ERC721AStorage.TokenApprovalRef storage tokenApproval = ERC721AStorage.layout()._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 payable 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.
                      --ERC721AStorage.layout()._packedAddressData[from]; // Updates: `balance -= 1`.
                      ++ERC721AStorage.layout()._packedAddressData[to]; // Updates: `balance += 1`.
                      // Updates:
                      // - `address` to the next owner.
                      // - `startTimestamp` to the timestamp of transfering.
                      // - `burned` to `false`.
                      // - `nextInitialized` to `true`.
                      ERC721AStorage.layout()._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 (ERC721AStorage.layout()._packedOwnerships[nextTokenId] == 0) {
                              // If the next slot is within bounds.
                              if (nextTokenId != ERC721AStorage.layout()._currentIndex) {
                                  // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                                  ERC721AStorage.layout()._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 payable 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 payable 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__IERC721ReceiverUpgradeable(to).onERC721Received(_msgSenderERC721A(), from, tokenId, _data)
                  returns (bytes4 retval) {
                      return retval == ERC721A__IERC721ReceiverUpgradeable(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 = ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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();
                      ERC721AStorage.layout()._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 = ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._packedOwnerships[startTokenId] = _packOwnershipData(
                          to,
                          _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
                      );
                      emit ConsecutiveTransfer(startTokenId, startTokenId + quantity - 1, address(0), to);
                      ERC721AStorage.layout()._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 = ERC721AStorage.layout()._currentIndex;
                          uint256 index = end - quantity;
                          do {
                              if (!_checkContractOnERC721Received(address(0), to, index++, _data)) {
                                  revert TransferToNonERC721ReceiverImplementer();
                              }
                          } while (index < end);
                          // Reentrancy protection.
                          if (ERC721AStorage.layout()._currentIndex != end) revert();
                      }
                  }
              }
              /**
               * @dev Equivalent to `_safeMint(to, quantity, '')`.
               */
              function _safeMint(address to, uint256 quantity) internal virtual {
                  _safeMint(to, quantity, '');
              }
              // =============================================================
              //                       APPROVAL OPERATIONS
              // =============================================================
              /**
               * @dev Equivalent to `_approve(to, tokenId, false)`.
               */
              function _approve(address to, uint256 tokenId) internal virtual {
                  _approve(to, tokenId, false);
              }
              /**
               * @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:
               *
               * - `tokenId` must exist.
               *
               * Emits an {Approval} event.
               */
              function _approve(
                  address to,
                  uint256 tokenId,
                  bool approvalCheck
              ) internal virtual {
                  address owner = ownerOf(tokenId);
                  if (approvalCheck)
                      if (_msgSenderERC721A() != owner)
                          if (!isApprovedForAll(owner, _msgSenderERC721A())) {
                              revert ApprovalCallerNotOwnerNorApproved();
                          }
                  ERC721AStorage.layout()._tokenApprovals[tokenId].value = to;
                  emit Approval(owner, to, tokenId);
              }
              // =============================================================
              //                        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;`.
                      ERC721AStorage.layout()._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`.
                      ERC721AStorage.layout()._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 (ERC721AStorage.layout()._packedOwnerships[nextTokenId] == 0) {
                              // If the next slot is within bounds.
                              if (nextTokenId != ERC721AStorage.layout()._currentIndex) {
                                  // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                                  ERC721AStorage.layout()._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 {
                      ERC721AStorage.layout()._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 = ERC721AStorage.layout()._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);
                  ERC721AStorage.layout()._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
          pragma solidity ^0.8.0;
          import {ERC4907AUpgradeable} from './ERC4907AUpgradeable.sol';
          library ERC4907AStorage {
              struct Layout {
                  // Mapping from token ID to user info.
                  //
                  // Bits Layout:
                  // - [0..159]   `user`
                  // - [160..223] `expires`
                  mapping(uint256 => uint256) _packedUserInfo;
              }
              bytes32 internal constant STORAGE_SLOT = keccak256('ERC721A.contracts.storage.ERC4907A');
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC4907AUpgradeable.sol';
          import '../ERC721AUpgradeable.sol';
          import {ERC4907AStorage} from './ERC4907AStorage.sol';
          import '../ERC721A__Initializable.sol';
          /**
           * @title ERC4907A
           *
           * @dev [ERC4907](https://eips.ethereum.org/EIPS/eip-4907) compliant
           * extension of ERC721A, which allows owners and authorized addresses
           * to add a time-limited role with restricted permissions to ERC721 tokens.
           */
          abstract contract ERC4907AUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, IERC4907AUpgradeable {
              using ERC4907AStorage for ERC4907AStorage.Layout;
              function __ERC4907A_init() internal onlyInitializingERC721A {
                  __ERC4907A_init_unchained();
              }
              function __ERC4907A_init_unchained() internal onlyInitializingERC721A {}
              // The bit position of `expires` in packed user info.
              uint256 private constant _BITPOS_EXPIRES = 160;
              /**
               * @dev Sets the `user` and `expires` for `tokenId`.
               * The zero address indicates there is no user.
               *
               * Requirements:
               *
               * - The caller must own `tokenId` or be an approved operator.
               */
              function setUser(
                  uint256 tokenId,
                  address user,
                  uint64 expires
              ) public virtual override {
                  // Require the caller to be either the token owner or an approved operator.
                  address owner = ownerOf(tokenId);
                  if (_msgSenderERC721A() != owner)
                      if (!isApprovedForAll(owner, _msgSenderERC721A()))
                          if (getApproved(tokenId) != _msgSenderERC721A()) revert SetUserCallerNotOwnerNorApproved();
                  ERC4907AStorage.layout()._packedUserInfo[tokenId] =
                      (uint256(expires) << _BITPOS_EXPIRES) |
                      uint256(uint160(user));
                  emit UpdateUser(tokenId, user, expires);
              }
              /**
               * @dev Returns the user address for `tokenId`.
               * The zero address indicates that there is no user or if the user is expired.
               */
              function userOf(uint256 tokenId) public view virtual override returns (address) {
                  uint256 packed = ERC4907AStorage.layout()._packedUserInfo[tokenId];
                  assembly {
                      // Branchless `packed *= (block.timestamp <= expires ? 1 : 0)`.
                      // If the `block.timestamp == expires`, the `lt` clause will be true
                      // if there is a non-zero user address in the lower 160 bits of `packed`.
                      packed := mul(
                          packed,
                          // `block.timestamp <= expires ? 1 : 0`.
                          lt(shl(_BITPOS_EXPIRES, timestamp()), packed)
                      )
                  }
                  return address(uint160(packed));
              }
              /**
               * @dev Returns the user's expires of `tokenId`.
               */
              function userExpires(uint256 tokenId) public view virtual override returns (uint256) {
                  return ERC4907AStorage.layout()._packedUserInfo[tokenId] >> _BITPOS_EXPIRES;
              }
              /**
               * @dev Override of {IERC165-supportsInterface}.
               */
              function supportsInterface(bytes4 interfaceId)
                  public
                  view
                  virtual
                  override(ERC721AUpgradeable, IERC721AUpgradeable)
                  returns (bool)
              {
                  // The interface ID for ERC4907 is `0xad092b5c`,
                  // as defined in [ERC4907](https://eips.ethereum.org/EIPS/eip-4907).
                  return super.supportsInterface(interfaceId) || interfaceId == 0xad092b5c;
              }
              /**
               * @dev Returns the user address for `tokenId`, ignoring the expiry status.
               */
              function _explicitUserOf(uint256 tokenId) internal view virtual returns (address) {
                  return address(uint160(ERC4907AStorage.layout()._packedUserInfo[tokenId]));
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import './IERC721AQueryableUpgradeable.sol';
          import '../ERC721AUpgradeable.sol';
          import '../ERC721A__Initializable.sol';
          /**
           * @title ERC721AQueryable.
           *
           * @dev ERC721A subclass with convenience query functions.
           */
          abstract contract ERC721AQueryableUpgradeable is
              ERC721A__Initializable,
              ERC721AUpgradeable,
              IERC721AQueryableUpgradeable
          {
              function __ERC721AQueryable_init() internal onlyInitializingERC721A {
                  __ERC721AQueryable_init_unchained();
              }
              function __ERC721AQueryable_init_unchained() internal onlyInitializingERC721A {}
              /**
               * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
               *
               * If the `tokenId` is out of bounds:
               *
               * - `addr = address(0)`
               * - `startTimestamp = 0`
               * - `burned = false`
               * - `extraData = 0`
               *
               * If the `tokenId` is burned:
               *
               * - `addr = <Address of owner before token was burned>`
               * - `startTimestamp = <Timestamp when token was burned>`
               * - `burned = true`
               * - `extraData = <Extra data when token was burned>`
               *
               * Otherwise:
               *
               * - `addr = <Address of owner>`
               * - `startTimestamp = <Timestamp of start of ownership>`
               * - `burned = false`
               * - `extraData = <Extra data at start of ownership>`
               */
              function explicitOwnershipOf(uint256 tokenId) public view virtual override returns (TokenOwnership memory) {
                  TokenOwnership memory ownership;
                  if (tokenId < _startTokenId() || tokenId >= _nextTokenId()) {
                      return ownership;
                  }
                  ownership = _ownershipAt(tokenId);
                  if (ownership.burned) {
                      return ownership;
                  }
                  return _ownershipOf(tokenId);
              }
              /**
               * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
               * See {ERC721AQueryable-explicitOwnershipOf}
               */
              function explicitOwnershipsOf(uint256[] calldata tokenIds)
                  external
                  view
                  virtual
                  override
                  returns (TokenOwnership[] memory)
              {
                  unchecked {
                      uint256 tokenIdsLength = tokenIds.length;
                      TokenOwnership[] memory ownerships = new TokenOwnership[](tokenIdsLength);
                      for (uint256 i; i != tokenIdsLength; ++i) {
                          ownerships[i] = explicitOwnershipOf(tokenIds[i]);
                      }
                      return ownerships;
                  }
              }
              /**
               * @dev Returns an array of token IDs owned by `owner`,
               * in the range [`start`, `stop`)
               * (i.e. `start <= tokenId < stop`).
               *
               * This function allows for tokens to be queried if the collection
               * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
               *
               * Requirements:
               *
               * - `start < stop`
               */
              function tokensOfOwnerIn(
                  address owner,
                  uint256 start,
                  uint256 stop
              ) external view virtual override returns (uint256[] memory) {
                  unchecked {
                      if (start >= stop) revert InvalidQueryRange();
                      uint256 tokenIdsIdx;
                      uint256 stopLimit = _nextTokenId();
                      // Set `start = max(start, _startTokenId())`.
                      if (start < _startTokenId()) {
                          start = _startTokenId();
                      }
                      // Set `stop = min(stop, stopLimit)`.
                      if (stop > stopLimit) {
                          stop = stopLimit;
                      }
                      uint256 tokenIdsMaxLength = balanceOf(owner);
                      // Set `tokenIdsMaxLength = min(balanceOf(owner), stop - start)`,
                      // to cater for cases where `balanceOf(owner)` is too big.
                      if (start < stop) {
                          uint256 rangeLength = stop - start;
                          if (rangeLength < tokenIdsMaxLength) {
                              tokenIdsMaxLength = rangeLength;
                          }
                      } else {
                          tokenIdsMaxLength = 0;
                      }
                      uint256[] memory tokenIds = new uint256[](tokenIdsMaxLength);
                      if (tokenIdsMaxLength == 0) {
                          return tokenIds;
                      }
                      // We need to call `explicitOwnershipOf(start)`,
                      // because the slot at `start` may not be initialized.
                      TokenOwnership memory ownership = explicitOwnershipOf(start);
                      address currOwnershipAddr;
                      // If the starting slot exists (i.e. not burned), initialize `currOwnershipAddr`.
                      // `ownership.address` will not be zero, as `start` is clamped to the valid token ID range.
                      if (!ownership.burned) {
                          currOwnershipAddr = ownership.addr;
                      }
                      for (uint256 i = start; i != stop && tokenIdsIdx != tokenIdsMaxLength; ++i) {
                          ownership = _ownershipAt(i);
                          if (ownership.burned) {
                              continue;
                          }
                          if (ownership.addr != address(0)) {
                              currOwnershipAddr = ownership.addr;
                          }
                          if (currOwnershipAddr == owner) {
                              tokenIds[tokenIdsIdx++] = i;
                          }
                      }
                      // Downsize the array to fit.
                      assembly {
                          mstore(tokenIds, tokenIdsIdx)
                      }
                      return tokenIds;
                  }
              }
              /**
               * @dev Returns an array of token IDs owned by `owner`.
               *
               * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
               * It is meant to be called off-chain.
               *
               * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
               * multiple smaller scans if the collection is large enough to cause
               * an out-of-gas error (10K collections should be fine).
               */
              function tokensOfOwner(address owner) external view virtual override returns (uint256[] memory) {
                  unchecked {
                      uint256 tokenIdsIdx;
                      address currOwnershipAddr;
                      uint256 tokenIdsLength = balanceOf(owner);
                      uint256[] memory tokenIds = new uint256[](tokenIdsLength);
                      TokenOwnership memory ownership;
                      for (uint256 i = _startTokenId(); tokenIdsIdx != tokenIdsLength; ++i) {
                          ownership = _ownershipAt(i);
                          if (ownership.burned) {
                              continue;
                          }
                          if (ownership.addr != address(0)) {
                              currOwnershipAddr = ownership.addr;
                          }
                          if (currOwnershipAddr == owner) {
                              tokenIds[tokenIdsIdx++] = i;
                          }
                      }
                      return tokenIds;
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import '../IERC721AUpgradeable.sol';
          /**
           * @dev Interface of ERC4907A.
           */
          interface IERC4907AUpgradeable is IERC721AUpgradeable {
              /**
               * The caller must own the token or be an approved operator.
               */
              error SetUserCallerNotOwnerNorApproved();
              /**
               * @dev Emitted when the `user` of an NFT or the `expires` of the `user` is changed.
               * The zero address for user indicates that there is no user address.
               */
              event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires);
              /**
               * @dev Sets the `user` and `expires` for `tokenId`.
               * The zero address indicates there is no user.
               *
               * Requirements:
               *
               * - The caller must own `tokenId` or be an approved operator.
               */
              function setUser(
                  uint256 tokenId,
                  address user,
                  uint64 expires
              ) external;
              /**
               * @dev Returns the user address for `tokenId`.
               * The zero address indicates that there is no user or if the user is expired.
               */
              function userOf(uint256 tokenId) external view returns (address);
              /**
               * @dev Returns the user's expires of `tokenId`.
               */
              function userExpires(uint256 tokenId) external view returns (uint256);
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          import '../IERC721AUpgradeable.sol';
          /**
           * @dev Interface of ERC721AQueryable.
           */
          interface IERC721AQueryableUpgradeable is IERC721AUpgradeable {
              /**
               * Invalid query range (`start` >= `stop`).
               */
              error InvalidQueryRange();
              /**
               * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
               *
               * If the `tokenId` is out of bounds:
               *
               * - `addr = address(0)`
               * - `startTimestamp = 0`
               * - `burned = false`
               * - `extraData = 0`
               *
               * If the `tokenId` is burned:
               *
               * - `addr = <Address of owner before token was burned>`
               * - `startTimestamp = <Timestamp when token was burned>`
               * - `burned = true`
               * - `extraData = <Extra data when token was burned>`
               *
               * Otherwise:
               *
               * - `addr = <Address of owner>`
               * - `startTimestamp = <Timestamp of start of ownership>`
               * - `burned = false`
               * - `extraData = <Extra data at start of ownership>`
               */
              function explicitOwnershipOf(uint256 tokenId) external view returns (TokenOwnership memory);
              /**
               * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
               * See {ERC721AQueryable-explicitOwnershipOf}
               */
              function explicitOwnershipsOf(uint256[] memory tokenIds) external view returns (TokenOwnership[] memory);
              /**
               * @dev Returns an array of token IDs owned by `owner`,
               * in the range [`start`, `stop`)
               * (i.e. `start <= tokenId < stop`).
               *
               * This function allows for tokens to be queried if the collection
               * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
               *
               * Requirements:
               *
               * - `start < stop`
               */
              function tokensOfOwnerIn(
                  address owner,
                  uint256 start,
                  uint256 stop
              ) external view returns (uint256[] memory);
              /**
               * @dev Returns an array of token IDs owned by `owner`.
               *
               * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
               * It is meant to be called off-chain.
               *
               * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
               * multiple smaller scans if the collection is large enough to cause
               * an out-of-gas error (10K collections should be fine).
               */
              function tokensOfOwner(address owner) external view returns (uint256[] memory);
          }
          // SPDX-License-Identifier: MIT
          // ERC721A Contracts v4.2.3
          // Creator: Chiru Labs
          pragma solidity ^0.8.4;
          /**
           * @dev Interface of ERC721A.
           */
          interface IERC721AUpgradeable {
              /**
               * 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 payable;
              /**
               * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
               */
              function safeTransferFrom(
                  address from,
                  address to,
                  uint256 tokenId
              ) external payable;
              /**
               * @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 payable;
              /**
               * @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 payable;
              /**
               * @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
          pragma solidity ^0.8.13;
          interface IOperatorFilterRegistry {
              /**
               * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
               *         true if supplied registrant address is not registered.
               */
              function isOperatorAllowed(address registrant, address operator) external view returns (bool);
              /**
               * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
               */
              function register(address registrant) external;
              /**
               * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
               */
              function registerAndSubscribe(address registrant, address subscription) external;
              /**
               * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
               *         address without subscribing.
               */
              function registerAndCopyEntries(address registrant, address registrantToCopy) external;
              /**
               * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
               *         Note that this does not remove any filtered addresses or codeHashes.
               *         Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
               */
              function unregister(address addr) external;
              /**
               * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
               */
              function updateOperator(address registrant, address operator, bool filtered) external;
              /**
               * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
               */
              function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
              /**
               * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
               */
              function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
              /**
               * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
               */
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
              /**
               * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
               *         subscription if present.
               *         Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
               *         subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
               *         used.
               */
              function subscribe(address registrant, address registrantToSubscribe) external;
              /**
               * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
               */
              function unsubscribe(address registrant, bool copyExistingEntries) external;
              /**
               * @notice Get the subscription address of a given registrant, if any.
               */
              function subscriptionOf(address addr) external returns (address registrant);
              /**
               * @notice Get the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscribers(address registrant) external returns (address[] memory);
              /**
               * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscriberAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
               */
              function copyEntriesOf(address registrant, address registrantToCopy) external;
              /**
               * @notice Returns true if operator is filtered by a given address or its subscription.
               */
              function isOperatorFiltered(address registrant, address operator) external returns (bool);
              /**
               * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
               */
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
              /**
               * @notice Returns true if a codeHash is filtered by a given address or its subscription.
               */
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
              /**
               * @notice Returns a list of filtered operators for a given address or its subscription.
               */
              function filteredOperators(address addr) external returns (address[] memory);
              /**
               * @notice Returns the set of filtered codeHashes for a given address or its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashes(address addr) external returns (bytes32[] memory);
              /**
               * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredOperatorAt(address registrant, uint256 index) external returns (address);
              /**
               * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
              /**
               * @notice Returns true if an address has registered
               */
              function isRegistered(address addr) external returns (bool);
              /**
               * @dev Convenience method to compute the code hash of an arbitrary contract
               */
              function codeHashOf(address addr) external returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {IOperatorFilterRegistry} from "../IOperatorFilterRegistry.sol";
          import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
          /**
           * @title  OperatorFiltererUpgradeable
           * @notice Abstract contract whose constructor automatically registers and optionally subscribes to or copies another
           *         registrant's entries in the OperatorFilterRegistry when the init function is called.
           * @dev    This smart contract is meant to be inherited by token contracts so they can use the following:
           *         - `onlyAllowedOperator` modifier for `transferFrom` and `safeTransferFrom` methods.
           *         - `onlyAllowedOperatorApproval` modifier for `approve` and `setApprovalForAll` methods.
           */
          abstract contract OperatorFiltererUpgradeable is Initializable {
              /// @notice Emitted when an operator is not allowed.
              error OperatorNotAllowed(address operator);
              IOperatorFilterRegistry constant OPERATOR_FILTER_REGISTRY =
                  IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E);
              /// @dev The upgradeable initialize function that should be called when the contract is being upgraded.
              function __OperatorFilterer_init(address subscriptionOrRegistrantToCopy, bool subscribe)
                  internal
                  onlyInitializing
              {
                  // If an inheriting token contract is deployed to a network without the registry deployed, the modifier
                  // will not revert, but the contract will need to be registered with the registry once it is deployed in
                  // order for the modifier to filter addresses.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      if (!OPERATOR_FILTER_REGISTRY.isRegistered(address(this))) {
                          if (subscribe) {
                              OPERATOR_FILTER_REGISTRY.registerAndSubscribe(address(this), subscriptionOrRegistrantToCopy);
                          } else {
                              if (subscriptionOrRegistrantToCopy != address(0)) {
                                  OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), subscriptionOrRegistrantToCopy);
                              } else {
                                  OPERATOR_FILTER_REGISTRY.register(address(this));
                              }
                          }
                      }
                  }
              }
              /**
               * @dev A helper modifier to check if the operator is allowed.
               */
              modifier onlyAllowedOperator(address from) virtual {
                  // Allow spending tokens from addresses with balance
                  // Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
                  // from an EOA.
                  if (from != msg.sender) {
                      _checkFilterOperator(msg.sender);
                  }
                  _;
              }
              /**
               * @dev A helper modifier to check if the operator approval is allowed.
               */
              modifier onlyAllowedOperatorApproval(address operator) virtual {
                  _checkFilterOperator(operator);
                  _;
              }
              /**
               * @dev A helper function to check if the operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting or
                      // upgraded contracts may specify their own OperatorFilterRegistry implementations, which may behave
                      // differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {OperatorFiltererUpgradeable} from "./OperatorFiltererUpgradeable.sol";
          /**
           * @title  Upgradeable storage layout for RevokableOperatorFiltererUpgradeable.
           * @notice Upgradeable contracts must use a storage layout that can be used across upgrades.
           *         Only append new variables to the end of the layout.
           */
          library RevokableOperatorFiltererUpgradeableStorage {
              struct Layout {
                  /// @dev Whether the OperatorFilterRegistry has been revoked.
                  bool _isOperatorFilterRegistryRevoked;
              }
              /// @dev The storage slot for the layout.
              bytes32 internal constant STORAGE_SLOT = keccak256("RevokableOperatorFiltererUpgradeable.contracts.storage");
              /// @dev The layout of the storage.
              function layout() internal pure returns (Layout storage l) {
                  bytes32 slot = STORAGE_SLOT;
                  assembly {
                      l.slot := slot
                  }
              }
          }
          /**
           * @title  RevokableOperatorFilterer
           * @notice This contract is meant to allow contracts to permanently opt out of the OperatorFilterRegistry. The Registry
           *         itself has an "unregister" function, but if the contract is ownable, the owner can re-register at any point.
           *         As implemented, this abstract contract allows the contract owner to toggle the
           *         isOperatorFilterRegistryRevoked flag in order to permanently bypass the OperatorFilterRegistry checks.
           */
          abstract contract RevokableOperatorFiltererUpgradeable is OperatorFiltererUpgradeable {
              using RevokableOperatorFiltererUpgradeableStorage for RevokableOperatorFiltererUpgradeableStorage.Layout;
              error OnlyOwner();
              error AlreadyRevoked();
              event OperatorFilterRegistryRevoked();
              function __RevokableOperatorFilterer_init(address subscriptionOrRegistrantToCopy, bool subscribe) internal {
                  OperatorFiltererUpgradeable.__OperatorFilterer_init(subscriptionOrRegistrantToCopy, subscribe);
              }
              /**
               * @dev A helper function to check if the operator is allowed.
               */
              function _checkFilterOperator(address operator) internal view virtual override {
                  // Check registry code length to facilitate testing in environments without a deployed registry.
                  if (
                      !RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked
                          && address(OPERATOR_FILTER_REGISTRY).code.length > 0
                  ) {
                      // under normal circumstances, this function will revert rather than return false, but inheriting or
                      // upgraded contracts may specify their own OperatorFilterRegistry implementations, which may behave
                      // differently
                      if (!OPERATOR_FILTER_REGISTRY.isOperatorAllowed(address(this), operator)) {
                          revert OperatorNotAllowed(operator);
                      }
                  }
              }
              /**
               * @notice Disable the isOperatorFilterRegistryRevoked flag. OnlyOwner.
               */
              function revokeOperatorFilterRegistry() external {
                  if (msg.sender != owner()) {
                      revert OnlyOwner();
                  }
                  if (RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked) {
                      revert AlreadyRevoked();
                  }
                  RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked = true;
                  emit OperatorFilterRegistryRevoked();
              }
              function isOperatorFilterRegistryRevoked() public view returns (bool) {
                  return RevokableOperatorFiltererUpgradeableStorage.layout()._isOperatorFilterRegistryRevoked;
              }
              /**
               * @dev assume the contract has an owner, but leave specific Ownable implementation up to inheriting contract
               */
              function owner() public view virtual returns (address);
          }
          

          File 6 of 6: OperatorFilterRegistry
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          abstract contract Ownable is Context {
              address private _owner;
              event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              /**
               * @dev Initializes the contract setting the deployer as the initial owner.
               */
              constructor() {
                  _transferOwnership(_msgSender());
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  _checkOwner();
                  _;
              }
              /**
               * @dev Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if the sender is not the owner.
               */
              function _checkOwner() internal view virtual {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing any functionality that is only available to the owner.
               */
              function renounceOwnership() public virtual onlyOwner {
                  _transferOwnership(address(0));
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Can only be called by the current owner.
               */
              function transferOwnership(address newOwner) public virtual onlyOwner {
                  require(newOwner != address(0), "Ownable: new owner is the zero address");
                  _transferOwnership(newOwner);
              }
              /**
               * @dev Transfers ownership of the contract to a new account (`newOwner`).
               * Internal function without access restriction.
               */
              function _transferOwnership(address newOwner) internal virtual {
                  address oldOwner = _owner;
                  _owner = newOwner;
                  emit OwnershipTransferred(oldOwner, newOwner);
              }
          }
          // SPDX-License-Identifier: MIT
          // 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.7.0) (utils/structs/EnumerableSet.sol)
          // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
          pragma solidity ^0.8.0;
          /**
           * @dev Library for managing
           * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
           * types.
           *
           * Sets have the following properties:
           *
           * - Elements are added, removed, and checked for existence in constant time
           * (O(1)).
           * - Elements are enumerated in O(n). No guarantees are made on the ordering.
           *
           * ```
           * contract Example {
           *     // Add the library methods
           *     using EnumerableSet for EnumerableSet.AddressSet;
           *
           *     // Declare a set state variable
           *     EnumerableSet.AddressSet private mySet;
           * }
           * ```
           *
           * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
           * and `uint256` (`UintSet`) are supported.
           *
           * [WARNING]
           * ====
           * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
           * unusable.
           * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
           *
           * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
           * array of EnumerableSet.
           * ====
           */
          library EnumerableSet {
              // To implement this library for multiple types with as little code
              // repetition as possible, we write it in terms of a generic Set type with
              // bytes32 values.
              // The Set implementation uses private functions, and user-facing
              // implementations (such as AddressSet) are just wrappers around the
              // underlying Set.
              // This means that we can only create new EnumerableSets for types that fit
              // in bytes32.
              struct Set {
                  // Storage of set values
                  bytes32[] _values;
                  // Position of the value in the `values` array, plus 1 because index 0
                  // means a value is not in the set.
                  mapping(bytes32 => uint256) _indexes;
              }
              /**
               * @dev Add a value to a set. O(1).
               *
               * Returns true if the value was added to the set, that is if it was not
               * already present.
               */
              function _add(Set storage set, bytes32 value) private returns (bool) {
                  if (!_contains(set, value)) {
                      set._values.push(value);
                      // The value is stored at length-1, but we add 1 to all indexes
                      // and use 0 as a sentinel value
                      set._indexes[value] = set._values.length;
                      return true;
                  } else {
                      return false;
                  }
              }
              /**
               * @dev Removes a value from a set. O(1).
               *
               * Returns true if the value was removed from the set, that is if it was
               * present.
               */
              function _remove(Set storage set, bytes32 value) private returns (bool) {
                  // We read and store the value's index to prevent multiple reads from the same storage slot
                  uint256 valueIndex = set._indexes[value];
                  if (valueIndex != 0) {
                      // Equivalent to contains(set, value)
                      // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                      // the array, and then remove the last element (sometimes called as 'swap and pop').
                      // This modifies the order of the array, as noted in {at}.
                      uint256 toDeleteIndex = valueIndex - 1;
                      uint256 lastIndex = set._values.length - 1;
                      if (lastIndex != toDeleteIndex) {
                          bytes32 lastValue = set._values[lastIndex];
                          // Move the last value to the index where the value to delete is
                          set._values[toDeleteIndex] = lastValue;
                          // Update the index for the moved value
                          set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
                      }
                      // Delete the slot where the moved value was stored
                      set._values.pop();
                      // Delete the index for the deleted slot
                      delete set._indexes[value];
                      return true;
                  } else {
                      return false;
                  }
              }
              /**
               * @dev Returns true if the value is in the set. O(1).
               */
              function _contains(Set storage set, bytes32 value) private view returns (bool) {
                  return set._indexes[value] != 0;
              }
              /**
               * @dev Returns the number of values on the set. O(1).
               */
              function _length(Set storage set) private view returns (uint256) {
                  return set._values.length;
              }
              /**
               * @dev Returns the value stored at position `index` in the set. O(1).
               *
               * Note that there are no guarantees on the ordering of values inside the
               * array, and it may change when more values are added or removed.
               *
               * Requirements:
               *
               * - `index` must be strictly less than {length}.
               */
              function _at(Set storage set, uint256 index) private view returns (bytes32) {
                  return set._values[index];
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function _values(Set storage set) private view returns (bytes32[] memory) {
                  return set._values;
              }
              // Bytes32Set
              struct Bytes32Set {
                  Set _inner;
              }
              /**
               * @dev Add a value to a set. O(1).
               *
               * Returns true if the value was added to the set, that is if it was not
               * already present.
               */
              function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                  return _add(set._inner, value);
              }
              /**
               * @dev Removes a value from a set. O(1).
               *
               * Returns true if the value was removed from the set, that is if it was
               * present.
               */
              function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                  return _remove(set._inner, value);
              }
              /**
               * @dev Returns true if the value is in the set. O(1).
               */
              function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
                  return _contains(set._inner, value);
              }
              /**
               * @dev Returns the number of values in the set. O(1).
               */
              function length(Bytes32Set storage set) internal view returns (uint256) {
                  return _length(set._inner);
              }
              /**
               * @dev Returns the value stored at position `index` in the set. O(1).
               *
               * Note that there are no guarantees on the ordering of values inside the
               * array, and it may change when more values are added or removed.
               *
               * Requirements:
               *
               * - `index` must be strictly less than {length}.
               */
              function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
                  return _at(set._inner, index);
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  bytes32[] memory result;
                  /// @solidity memory-safe-assembly
                  assembly {
                      result := store
                  }
                  return result;
              }
              // AddressSet
              struct AddressSet {
                  Set _inner;
              }
              /**
               * @dev Add a value to a set. O(1).
               *
               * Returns true if the value was added to the set, that is if it was not
               * already present.
               */
              function add(AddressSet storage set, address value) internal returns (bool) {
                  return _add(set._inner, bytes32(uint256(uint160(value))));
              }
              /**
               * @dev Removes a value from a set. O(1).
               *
               * Returns true if the value was removed from the set, that is if it was
               * present.
               */
              function remove(AddressSet storage set, address value) internal returns (bool) {
                  return _remove(set._inner, bytes32(uint256(uint160(value))));
              }
              /**
               * @dev Returns true if the value is in the set. O(1).
               */
              function contains(AddressSet storage set, address value) internal view returns (bool) {
                  return _contains(set._inner, bytes32(uint256(uint160(value))));
              }
              /**
               * @dev Returns the number of values in the set. O(1).
               */
              function length(AddressSet storage set) internal view returns (uint256) {
                  return _length(set._inner);
              }
              /**
               * @dev Returns the value stored at position `index` in the set. O(1).
               *
               * Note that there are no guarantees on the ordering of values inside the
               * array, and it may change when more values are added or removed.
               *
               * Requirements:
               *
               * - `index` must be strictly less than {length}.
               */
              function at(AddressSet storage set, uint256 index) internal view returns (address) {
                  return address(uint160(uint256(_at(set._inner, index))));
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(AddressSet storage set) internal view returns (address[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  address[] memory result;
                  /// @solidity memory-safe-assembly
                  assembly {
                      result := store
                  }
                  return result;
              }
              // UintSet
              struct UintSet {
                  Set _inner;
              }
              /**
               * @dev Add a value to a set. O(1).
               *
               * Returns true if the value was added to the set, that is if it was not
               * already present.
               */
              function add(UintSet storage set, uint256 value) internal returns (bool) {
                  return _add(set._inner, bytes32(value));
              }
              /**
               * @dev Removes a value from a set. O(1).
               *
               * Returns true if the value was removed from the set, that is if it was
               * present.
               */
              function remove(UintSet storage set, uint256 value) internal returns (bool) {
                  return _remove(set._inner, bytes32(value));
              }
              /**
               * @dev Returns true if the value is in the set. O(1).
               */
              function contains(UintSet storage set, uint256 value) internal view returns (bool) {
                  return _contains(set._inner, bytes32(value));
              }
              /**
               * @dev Returns the number of values in the set. O(1).
               */
              function length(UintSet storage set) internal view returns (uint256) {
                  return _length(set._inner);
              }
              /**
               * @dev Returns the value stored at position `index` in the set. O(1).
               *
               * Note that there are no guarantees on the ordering of values inside the
               * array, and it may change when more values are added or removed.
               *
               * Requirements:
               *
               * - `index` must be strictly less than {length}.
               */
              function at(UintSet storage set, uint256 index) internal view returns (uint256) {
                  return uint256(_at(set._inner, index));
              }
              /**
               * @dev Return the entire set in an array
               *
               * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
               * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
               * this function has an unbounded cost, and using it as part of a state-changing function may render the function
               * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
               */
              function values(UintSet storage set) internal view returns (uint256[] memory) {
                  bytes32[] memory store = _values(set._inner);
                  uint256[] memory result;
                  /// @solidity memory-safe-assembly
                  assembly {
                      result := store
                  }
                  return result;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
          interface IOperatorFilterRegistry {
              function isOperatorAllowed(address registrant, address operator) external returns (bool);
              function register(address registrant) external;
              function registerAndSubscribe(address registrant, address subscription) external;
              function registerAndCopyEntries(address registrant, address registrantToCopy) external;
              function updateOperator(address registrant, address operator, bool filtered) external;
              function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
              function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
              function subscribe(address registrant, address registrantToSubscribe) external;
              function unsubscribe(address registrant, bool copyExistingEntries) external;
              function subscriptionOf(address addr) external returns (address registrant);
              function subscribers(address registrant) external returns (address[] memory);
              function subscriberAt(address registrant, uint256 index) external returns (address);
              function copyEntriesOf(address registrant, address registrantToCopy) external;
              function isOperatorFiltered(address registrant, address operator) external returns (bool);
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
              function filteredOperators(address addr) external returns (address[] memory);
              function filteredCodeHashes(address addr) external returns (bytes32[] memory);
              function filteredOperatorAt(address registrant, uint256 index) external returns (address);
              function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
              function isRegistered(address addr) external returns (bool);
              function codeHashOf(address addr) external returns (bytes32);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          import {IOperatorFilterRegistry} from "./IOperatorFilterRegistry.sol";
          import {Ownable} from "openzeppelin-contracts/access/Ownable.sol";
          import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
          import {OperatorFilterRegistryErrorsAndEvents} from "./OperatorFilterRegistryErrorsAndEvents.sol";
          /**
           * @title  OperatorFilterRegistry
           * @notice Borrows heavily from the QQL BlacklistOperatorFilter contract:
           *         https://github.com/qql-art/contracts/blob/main/contracts/BlacklistOperatorFilter.sol
           * @notice This contracts allows tokens or token owners to register specific addresses or codeHashes that may be
           * *       restricted according to the isOperatorAllowed function.
           */
          contract OperatorFilterRegistry is IOperatorFilterRegistry, OperatorFilterRegistryErrorsAndEvents {
              using EnumerableSet for EnumerableSet.AddressSet;
              using EnumerableSet for EnumerableSet.Bytes32Set;
              /// @dev initialized accounts have a nonzero codehash (see https://eips.ethereum.org/EIPS/eip-1052)
              /// Note that this will also be a smart contract's codehash when making calls from its constructor.
              bytes32 constant EOA_CODEHASH = keccak256("");
              mapping(address => EnumerableSet.AddressSet) private _filteredOperators;
              mapping(address => EnumerableSet.Bytes32Set) private _filteredCodeHashes;
              mapping(address => address) private _registrations;
              mapping(address => EnumerableSet.AddressSet) private _subscribers;
              /**
               * @notice restricts method caller to the address or EIP-173 "owner()"
               */
              modifier onlyAddressOrOwner(address addr) {
                  if (msg.sender != addr) {
                      try Ownable(addr).owner() returns (address owner) {
                          if (msg.sender != owner) {
                              revert OnlyAddressOrOwner();
                          }
                      } catch (bytes memory reason) {
                          if (reason.length == 0) {
                              revert NotOwnable();
                          } else {
                              /// @solidity memory-safe-assembly
                              assembly {
                                  revert(add(32, reason), mload(reason))
                              }
                          }
                      }
                  }
                  _;
              }
              /**
               * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
               *         true if supplied registrant address is not registered.
               */
              function isOperatorAllowed(address registrant, address operator) external view returns (bool) {
                  address registration = _registrations[registrant];
                  if (registration != address(0)) {
                      EnumerableSet.AddressSet storage filteredOperatorsRef;
                      EnumerableSet.Bytes32Set storage filteredCodeHashesRef;
                      filteredOperatorsRef = _filteredOperators[registration];
                      filteredCodeHashesRef = _filteredCodeHashes[registration];
                      if (filteredOperatorsRef.contains(operator)) {
                          revert AddressFiltered(operator);
                      }
                      if (operator.code.length > 0) {
                          bytes32 codeHash = operator.codehash;
                          if (filteredCodeHashesRef.contains(codeHash)) {
                              revert CodeHashFiltered(operator, codeHash);
                          }
                      }
                  }
                  return true;
              }
              //////////////////
              // AUTH METHODS //
              //////////////////
              /**
               * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
               */
              function register(address registrant) external onlyAddressOrOwner(registrant) {
                  if (_registrations[registrant] != address(0)) {
                      revert AlreadyRegistered();
                  }
                  _registrations[registrant] = registrant;
                  emit RegistrationUpdated(registrant, true);
              }
              /**
               * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
               *         Note that this does not remove any filtered addresses or codeHashes.
               *         Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
               */
              function unregister(address registrant) external onlyAddressOrOwner(registrant) {
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration != registrant) {
                      _subscribers[registration].remove(registrant);
                      emit SubscriptionUpdated(registrant, registration, false);
                  }
                  _registrations[registrant] = address(0);
                  emit RegistrationUpdated(registrant, false);
              }
              /**
               * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
               */
              function registerAndSubscribe(address registrant, address subscription) external onlyAddressOrOwner(registrant) {
                  address registration = _registrations[registrant];
                  if (registration != address(0)) {
                      revert AlreadyRegistered();
                  }
                  if (registrant == subscription) {
                      revert CannotSubscribeToSelf();
                  }
                  address subscriptionRegistration = _registrations[subscription];
                  if (subscriptionRegistration == address(0)) {
                      revert NotRegistered(subscription);
                  }
                  if (subscriptionRegistration != subscription) {
                      revert CannotSubscribeToRegistrantWithSubscription(subscription);
                  }
                  _registrations[registrant] = subscription;
                  _subscribers[subscription].add(registrant);
                  emit RegistrationUpdated(registrant, true);
                  emit SubscriptionUpdated(registrant, subscription, true);
              }
              /**
               * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
               *         address without subscribing.
               */
              function registerAndCopyEntries(address registrant, address registrantToCopy)
                  external
                  onlyAddressOrOwner(registrant)
              {
                  if (registrantToCopy == registrant) {
                      revert CannotCopyFromSelf();
                  }
                  address registration = _registrations[registrant];
                  if (registration != address(0)) {
                      revert AlreadyRegistered();
                  }
                  address registrantRegistration = _registrations[registrantToCopy];
                  if (registrantRegistration == address(0)) {
                      revert NotRegistered(registrantToCopy);
                  }
                  _registrations[registrant] = registrant;
                  emit RegistrationUpdated(registrant, true);
                  _copyEntries(registrant, registrantToCopy);
              }
              /**
               * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
               */
              function updateOperator(address registrant, address operator, bool filtered)
                  external
                  onlyAddressOrOwner(registrant)
              {
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration != registrant) {
                      revert CannotUpdateWhileSubscribed(registration);
                  }
                  EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrant];
                  if (!filtered) {
                      bool removed = filteredOperatorsRef.remove(operator);
                      if (!removed) {
                          revert AddressNotFiltered(operator);
                      }
                  } else {
                      bool added = filteredOperatorsRef.add(operator);
                      if (!added) {
                          revert AddressAlreadyFiltered(operator);
                      }
                  }
                  emit OperatorUpdated(registrant, operator, filtered);
              }
              /**
               * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
               */
              function updateCodeHash(address registrant, bytes32 codeHash, bool filtered)
                  external
                  onlyAddressOrOwner(registrant)
              {
                  if (codeHash == EOA_CODEHASH) {
                      revert CannotFilterEOAs();
                  }
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration != registrant) {
                      revert CannotUpdateWhileSubscribed(registration);
                  }
                  EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrant];
                  if (!filtered) {
                      bool removed = filteredCodeHashesRef.remove(codeHash);
                      if (!removed) {
                          revert CodeHashNotFiltered(codeHash);
                      }
                  } else {
                      bool added = filteredCodeHashesRef.add(codeHash);
                      if (!added) {
                          revert CodeHashAlreadyFiltered(codeHash);
                      }
                  }
                  emit CodeHashUpdated(registrant, codeHash, filtered);
              }
              /**
               * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
               */
              function updateOperators(address registrant, address[] calldata operators, bool filtered)
                  external
                  onlyAddressOrOwner(registrant)
              {
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration != registrant) {
                      revert CannotUpdateWhileSubscribed(registration);
                  }
                  EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrant];
                  uint256 operatorsLength = operators.length;
                  unchecked {
                      if (!filtered) {
                          for (uint256 i = 0; i < operatorsLength; ++i) {
                              address operator = operators[i];
                              bool removed = filteredOperatorsRef.remove(operator);
                              if (!removed) {
                                  revert AddressNotFiltered(operator);
                              }
                          }
                      } else {
                          for (uint256 i = 0; i < operatorsLength; ++i) {
                              address operator = operators[i];
                              bool added = filteredOperatorsRef.add(operator);
                              if (!added) {
                                  revert AddressAlreadyFiltered(operator);
                              }
                          }
                      }
                  }
                  emit OperatorsUpdated(registrant, operators, filtered);
              }
              /**
               * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
               */
              function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered)
                  external
                  onlyAddressOrOwner(registrant)
              {
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration != registrant) {
                      revert CannotUpdateWhileSubscribed(registration);
                  }
                  EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrant];
                  uint256 codeHashesLength = codeHashes.length;
                  unchecked {
                      if (!filtered) {
                          for (uint256 i = 0; i < codeHashesLength; ++i) {
                              bytes32 codeHash = codeHashes[i];
                              bool removed = filteredCodeHashesRef.remove(codeHash);
                              if (!removed) {
                                  revert CodeHashNotFiltered(codeHash);
                              }
                          }
                      } else {
                          for (uint256 i = 0; i < codeHashesLength; ++i) {
                              bytes32 codeHash = codeHashes[i];
                              if (codeHash == EOA_CODEHASH) {
                                  revert CannotFilterEOAs();
                              }
                              bool added = filteredCodeHashesRef.add(codeHash);
                              if (!added) {
                                  revert CodeHashAlreadyFiltered(codeHash);
                              }
                          }
                      }
                  }
                  emit CodeHashesUpdated(registrant, codeHashes, filtered);
              }
              /**
               * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
               *         subscription if present.
               *         Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
               *         subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
               *         used.
               */
              function subscribe(address registrant, address newSubscription) external onlyAddressOrOwner(registrant) {
                  if (registrant == newSubscription) {
                      revert CannotSubscribeToSelf();
                  }
                  if (newSubscription == address(0)) {
                      revert CannotSubscribeToZeroAddress();
                  }
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration == newSubscription) {
                      revert AlreadySubscribed(newSubscription);
                  }
                  address newSubscriptionRegistration = _registrations[newSubscription];
                  if (newSubscriptionRegistration == address(0)) {
                      revert NotRegistered(newSubscription);
                  }
                  if (newSubscriptionRegistration != newSubscription) {
                      revert CannotSubscribeToRegistrantWithSubscription(newSubscription);
                  }
                  if (registration != registrant) {
                      _subscribers[registration].remove(registrant);
                      emit SubscriptionUpdated(registrant, registration, false);
                  }
                  _registrations[registrant] = newSubscription;
                  _subscribers[newSubscription].add(registrant);
                  emit SubscriptionUpdated(registrant, newSubscription, true);
              }
              /**
               * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
               */
              function unsubscribe(address registrant, bool copyExistingEntries) external onlyAddressOrOwner(registrant) {
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration == registrant) {
                      revert NotSubscribed();
                  }
                  _subscribers[registration].remove(registrant);
                  _registrations[registrant] = registrant;
                  emit SubscriptionUpdated(registrant, registration, false);
                  if (copyExistingEntries) {
                      _copyEntries(registrant, registration);
                  }
              }
              /**
               * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
               */
              function copyEntriesOf(address registrant, address registrantToCopy) external onlyAddressOrOwner(registrant) {
                  if (registrant == registrantToCopy) {
                      revert CannotCopyFromSelf();
                  }
                  address registration = _registrations[registrant];
                  if (registration == address(0)) {
                      revert NotRegistered(registrant);
                  }
                  if (registration != registrant) {
                      revert CannotUpdateWhileSubscribed(registration);
                  }
                  address registrantRegistration = _registrations[registrantToCopy];
                  if (registrantRegistration == address(0)) {
                      revert NotRegistered(registrantToCopy);
                  }
                  _copyEntries(registrant, registrantToCopy);
              }
              /// @dev helper to copy entries from registrantToCopy to registrant and emit events
              function _copyEntries(address registrant, address registrantToCopy) private {
                  EnumerableSet.AddressSet storage filteredOperatorsRef = _filteredOperators[registrantToCopy];
                  EnumerableSet.Bytes32Set storage filteredCodeHashesRef = _filteredCodeHashes[registrantToCopy];
                  uint256 filteredOperatorsLength = filteredOperatorsRef.length();
                  uint256 filteredCodeHashesLength = filteredCodeHashesRef.length();
                  unchecked {
                      for (uint256 i = 0; i < filteredOperatorsLength; ++i) {
                          address operator = filteredOperatorsRef.at(i);
                          bool added = _filteredOperators[registrant].add(operator);
                          if (added) {
                              emit OperatorUpdated(registrant, operator, true);
                          }
                      }
                      for (uint256 i = 0; i < filteredCodeHashesLength; ++i) {
                          bytes32 codehash = filteredCodeHashesRef.at(i);
                          bool added = _filteredCodeHashes[registrant].add(codehash);
                          if (added) {
                              emit CodeHashUpdated(registrant, codehash, true);
                          }
                      }
                  }
              }
              //////////////////
              // VIEW METHODS //
              //////////////////
              /**
               * @notice Get the subscription address of a given registrant, if any.
               */
              function subscriptionOf(address registrant) external view returns (address subscription) {
                  subscription = _registrations[registrant];
                  if (subscription == address(0)) {
                      revert NotRegistered(registrant);
                  } else if (subscription == registrant) {
                      subscription = address(0);
                  }
              }
              /**
               * @notice Get the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscribers(address registrant) external view returns (address[] memory) {
                  return _subscribers[registrant].values();
              }
              /**
               * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
               *         Note that order is not guaranteed as updates are made.
               */
              function subscriberAt(address registrant, uint256 index) external view returns (address) {
                  return _subscribers[registrant].at(index);
              }
              /**
               * @notice Returns true if operator is filtered by a given address or its subscription.
               */
              function isOperatorFiltered(address registrant, address operator) external view returns (bool) {
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredOperators[registration].contains(operator);
                  }
                  return _filteredOperators[registrant].contains(operator);
              }
              /**
               * @notice Returns true if a codeHash is filtered by a given address or its subscription.
               */
              function isCodeHashFiltered(address registrant, bytes32 codeHash) external view returns (bool) {
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredCodeHashes[registration].contains(codeHash);
                  }
                  return _filteredCodeHashes[registrant].contains(codeHash);
              }
              /**
               * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
               */
              function isCodeHashOfFiltered(address registrant, address operatorWithCode) external view returns (bool) {
                  bytes32 codeHash = operatorWithCode.codehash;
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredCodeHashes[registration].contains(codeHash);
                  }
                  return _filteredCodeHashes[registrant].contains(codeHash);
              }
              /**
               * @notice Returns true if an address has registered
               */
              function isRegistered(address registrant) external view returns (bool) {
                  return _registrations[registrant] != address(0);
              }
              /**
               * @notice Returns a list of filtered operators for a given address or its subscription.
               */
              function filteredOperators(address registrant) external view returns (address[] memory) {
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredOperators[registration].values();
                  }
                  return _filteredOperators[registrant].values();
              }
              /**
               * @notice Returns the set of filtered codeHashes for a given address or its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashes(address registrant) external view returns (bytes32[] memory) {
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredCodeHashes[registration].values();
                  }
                  return _filteredCodeHashes[registrant].values();
              }
              /**
               * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredOperatorAt(address registrant, uint256 index) external view returns (address) {
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredOperators[registration].at(index);
                  }
                  return _filteredOperators[registrant].at(index);
              }
              /**
               * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
               *         its subscription.
               *         Note that order is not guaranteed as updates are made.
               */
              function filteredCodeHashAt(address registrant, uint256 index) external view returns (bytes32) {
                  address registration = _registrations[registrant];
                  if (registration != registrant) {
                      return _filteredCodeHashes[registration].at(index);
                  }
                  return _filteredCodeHashes[registrant].at(index);
              }
              /// @dev Convenience method to compute the code hash of an arbitrary contract
              function codeHashOf(address a) external view returns (bytes32) {
                  return a.codehash;
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.13;
          contract OperatorFilterRegistryErrorsAndEvents {
              error CannotFilterEOAs();
              error AddressAlreadyFiltered(address operator);
              error AddressNotFiltered(address operator);
              error CodeHashAlreadyFiltered(bytes32 codeHash);
              error CodeHashNotFiltered(bytes32 codeHash);
              error OnlyAddressOrOwner();
              error NotRegistered(address registrant);
              error AlreadyRegistered();
              error AlreadySubscribed(address subscription);
              error NotSubscribed();
              error CannotUpdateWhileSubscribed(address subscription);
              error CannotSubscribeToSelf();
              error CannotSubscribeToZeroAddress();
              error NotOwnable();
              error AddressFiltered(address filtered);
              error CodeHashFiltered(address account, bytes32 codeHash);
              error CannotSubscribeToRegistrantWithSubscription(address registrant);
              error CannotCopyFromSelf();
              event RegistrationUpdated(address indexed registrant, bool indexed registered);
              event OperatorUpdated(address indexed registrant, address indexed operator, bool indexed filtered);
              event OperatorsUpdated(address indexed registrant, address[] operators, bool indexed filtered);
              event CodeHashUpdated(address indexed registrant, bytes32 indexed codeHash, bool indexed filtered);
              event CodeHashesUpdated(address indexed registrant, bytes32[] codeHashes, bool indexed filtered);
              event SubscriptionUpdated(address indexed registrant, address indexed subscription, bool indexed subscribed);
          }