ETH Price: $2,431.80 (+0.41%)

Transaction Decoder

Block:
19746435 at Apr-27-2024 11:41:23 AM +UTC
Transaction Fee:
0.000425709967481568 ETH $1.04
Gas Used:
60,389 Gas / 7.049462112 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x1B8719e6...962759C74
0x34b33C65...6A4bc20a0
0.009747180557109986 Eth
Nonce: 4
0.009321470589628418 Eth
Nonce: 5
0.000425709967481568
(Lido: Execution Layer Rewards Vault)
120.121480362104523342 Eth120.121480947877823342 Eth0.0000005857733

Execution Trace

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

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

      File 3 of 3: StrictAuthorizedTransferSecurityRegistry
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.24;
      import {
          ListTypes,
          TransferSecurityLevels,
          IStrictAuthorizedTransferSecurityRegistry
      } from "./interfaces/IStrictAuthorizedTransferSecurityRegistry.sol";
      import {
          ICreatorTokenTransferValidator
      } from "./interfaces/ICreatorTokenTransferValidator.sol";
      import { IOwnable } from "./interfaces/IOwnable.sol";
      import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
      import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
      import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
      import { Tstorish } from "tstorish/Tstorish.sol";
      import { IEOARegistry } from "./interfaces/IEOARegistry.sol";
      import {
          StrictAuthorizedTransferSecurityRegistryExtraViewFns
      } from "./StrictAuthorizedTransferSecurityRegistryExtraViewFns.sol";
      /// @title StrictAuthorizedTransferSecurityRegistry
      /// @dev Implementation of a simplified version of the Transfer Security Registry that only
      ///      supports authorizers and whitelisted operators, and allows collections to disable
      ///      direct transfers (where caller == from) and contract recipients (requiring EOA
      ///      registration by providing a signature). Note that a number of view functions on
      ///      collections that add this validator will not work.
      contract StrictAuthorizedTransferSecurityRegistry is Tstorish, IStrictAuthorizedTransferSecurityRegistry, ERC165 {
          using EnumerableSet for EnumerableSet.AddressSet;
          /**
           * @dev This struct is used internally to represent an enumerable list of accounts.
           */
          struct AccountList {
              EnumerableSet.AddressSet enumerableAccounts;
              mapping (address => bool) nonEnumerableAccounts;
          }
          /**
           * @dev This struct is used internally for the storage of authorizer + operator lists.
           */
          struct List {
              address owner;
              AccountList authorizers;
              AccountList operators;
              AccountList blacklist;
          }
          struct CollectionConfiguration {
              uint120 listId;
              bool policyBypassed;
              bool blacklistBased;
              bool directTransfersDisabled;
              bool contractRecipientsDisabled;
              bool signatureRegistrationRequired;
          }
          
          /// @dev The default admin role value for contracts that implement access control.
          bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;
          /// @notice Keeps track of the most recently created list id.
          uint120 public lastListId;
          /// @dev Mapping of list ids to list settings
          mapping (uint120 => List) private lists;
          /// @dev Mapping of collection addresses to list ids & security policies.
          mapping (address => CollectionConfiguration) private collectionConfiguration;
          // TSTORE slot: scope ++ 8 empty bytes ++ collection
          bytes4 private constant _AUTHORIZED_OPERATOR_SCOPE = 0x596a397a;
          // TSTORE slot: keccak256(scope ++ identifier ++ collection)
          bytes4 private constant _AUTHORIZED_IDENTIFIER_SCOPE = 0x7e746c61;
          // TSTORE slot: keccak256(scope ++ identifier ++ collection)
          bytes4 private constant _AUTHORIZED_AMOUNT_SCOPE = 0x71836d45;
          address private immutable _EXTRA_VIEW_FUNCTIONS;
          IEOARegistry private immutable _EOA_REGISTRY;
          /**
           * @dev This modifier restricts a function call to the owner of the list `id`.
           * @dev Throws when the caller is not the list owner.
           */
          modifier onlyListOwner(uint120 id) {
              _requireCallerOwnsList(id);
              _;
          }
          /**
           * @dev This modifier reverts a transaction if the supplied array has a zero length.
           * @dev Throws when the array parameter has a zero length.
           */
          modifier notZero(uint256 value) {
              if (value == 0) {
                  revert StrictAuthorizedTransferSecurityRegistry__ArrayLengthCannotBeZero();
              }
              _;
          }
          constructor(address defaultOwner, address eoaRegistry) {
              uint120 id = 0;
              lists[id].owner = defaultOwner;
              emit CreatedList(id, "DEFAULT LIST");
              emit ReassignedListOwnership(id, defaultOwner);
              // Deploy a contract containing legacy view functions.
              _EXTRA_VIEW_FUNCTIONS = address(new StrictAuthorizedTransferSecurityRegistryExtraViewFns());
              _EOA_REGISTRY = IEOARegistry(eoaRegistry);
          }
          // Delegatecall to contract with legacy view functions in the fallback.
          fallback() external {
              address target = _EXTRA_VIEW_FUNCTIONS;
              assembly {
                  calldatacopy(0, 0, calldatasize())
                  let status := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
                  returndatacopy(0, 0, returndatasize())
                  switch status
                  case 0 {
                      revert(0, returndatasize())
                  }
                  default {
                      return(0, returndatasize())
                  }
              }
          }
          /// Manage lists of authorizers & operators that can be applied to collections
          function createList(string calldata name) external returns (uint120) {
              uint120 id = ++lastListId;
              lists[id].owner = msg.sender;
              emit CreatedList(id, name);
              emit ReassignedListOwnership(id, msg.sender);
              return id;
          }
          function createListCopy(string calldata name, uint120 sourceListId) external override returns (uint120) {
              uint120 id = ++lastListId;
              unchecked {
                  if (sourceListId > id - 1) {
                      revert StrictAuthorizedTransferSecurityRegistry__ListDoesNotExist();
                  }
              }
              List storage sourceList = lists[sourceListId];
              List storage targetList = lists[id];
              targetList.owner = msg.sender;
              emit CreatedList(id, name);
              emit ReassignedListOwnership(id, msg.sender);
              _copyAddressSet(ListTypes.AuthorizerList, id, sourceList.authorizers, targetList.authorizers);
              _copyAddressSet(ListTypes.OperatorList, id, sourceList.operators, targetList.operators);
              _copyAddressSet(ListTypes.OperatorRequiringAuthorizationList, id, sourceList.blacklist, targetList.blacklist);
              return id;
          }
          function reassignOwnershipOfList(uint120 id, address newOwner) external onlyListOwner(id) {
              if (newOwner == address(0)) {
                  revert StrictAuthorizedTransferSecurityRegistry__ListOwnershipCannotBeTransferredToZeroAddress();
              }
              lists[id].owner = newOwner;
              emit ReassignedListOwnership(id, newOwner);
          }
          function renounceOwnershipOfList(uint120 id) external onlyListOwner(id) {
              lists[id].owner = address(0);
              emit ReassignedListOwnership(id, address(0));
          }
          function applyListToCollection(address collection, uint120 id) external {
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              if (id > lastListId) {
                  revert StrictAuthorizedTransferSecurityRegistry__ListDoesNotExist();
              }
              collectionConfiguration[collection].listId = id;
              emit AppliedListToCollection(collection, id);
          }
          function listOwners(uint120 id) external view returns (address) {
              return lists[id].owner;
          }
          /// Manage and query for authorizers on lists
          function addAccountToAuthorizers(uint120 id, address account) external onlyListOwner(id) {
              address[] memory accounts = new address[](1);
              accounts[0] = account;
              _addAccounts(id, accounts, lists[id].authorizers, ListTypes.AuthorizerList);
          }
          function addAccountsToAuthorizers(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _addAccounts(id, accounts, lists[id].authorizers, ListTypes.AuthorizerList);
          }
          function addAuthorizers(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _addAccounts(id, accounts, lists[id].authorizers, ListTypes.AuthorizerList);
          }
          function removeAccountFromAuthorizers(uint120 id, address account) external onlyListOwner(id) {
              address[] memory accounts = new address[](1);
              accounts[0] = account;
              _removeAccounts(id, accounts, lists[id].authorizers, ListTypes.AuthorizerList);
          }
          
          function removeAccountsFromAuthorizers(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _removeAccounts(id, accounts, lists[id].authorizers, ListTypes.AuthorizerList);
          }
          function getAuthorizerAccounts(uint120 id) external view returns (address[] memory) {
              return lists[id].authorizers.enumerableAccounts.values();
          }
          function isAccountAuthorizer(uint120 id, address account) external view returns (bool) {
              return lists[id].authorizers.nonEnumerableAccounts[account];
          }
          function getAuthorizerAccountsByCollection(address collection) external view returns (address[] memory) {
              return lists[collectionConfiguration[collection].listId].authorizers.enumerableAccounts.values();
          }
          function isAccountAuthorizerOfCollection(address collection, address account) external view returns (bool) {
              return lists[collectionConfiguration[collection].listId].authorizers.nonEnumerableAccounts[account];
          }
          function _ensureCallerIsCollectionAuthorizer(address collection) internal view {
              if (!lists[collectionConfiguration[collection].listId].authorizers.nonEnumerableAccounts[msg.sender]) {
                  revert StrictAuthorizedTransferSecurityRegistry__CallerIsNotValidAuthorizer();
              }
          }
          /// Manage and query for operators on lists
          function addAccountToWhitelist(uint120 id, address account) external onlyListOwner(id) {
              address[] memory accounts = new address[](1);
              accounts[0] = account;
              _addAccounts(id, accounts, lists[id].operators, ListTypes.OperatorList);
          }
          function addAccountsToWhitelist(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _addAccounts(id, accounts, lists[id].operators, ListTypes.OperatorList);
          }
          function addOperators(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _addAccounts(id, accounts, lists[id].operators, ListTypes.OperatorList);
          }
          function removeAccountFromWhitelist(uint120 id, address account) external onlyListOwner(id) {
              address[] memory accounts = new address[](1);
              accounts[0] = account;
              _removeAccounts(id, accounts, lists[id].operators, ListTypes.OperatorList);
          }
          function removeAccountsFromWhitelist(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _removeAccounts(id, accounts, lists[id].operators, ListTypes.OperatorList);
          }
          
          function getWhitelistedAccounts(uint120 id) external view returns (address[] memory) {
              return lists[id].operators.enumerableAccounts.values();
          }
          function isAccountWhitelisted(uint120 id, address account) external view returns (bool) {
              return lists[id].operators.nonEnumerableAccounts[account];
          }
          function getWhitelistedAccountsByCollection(address collection) external view returns (address[] memory) {
              return lists[collectionConfiguration[collection].listId].operators.enumerableAccounts.values();
          }
          function isAccountWhitelistedByCollection(address collection, address account) external view returns (bool) {
              return lists[collectionConfiguration[collection].listId].operators.nonEnumerableAccounts[account];
          }
          /// Manage and query for blacklists on lists
          function addAccountToBlacklist(uint120 id, address account) external onlyListOwner(id) {
              address[] memory accounts = new address[](1);
              accounts[0] = account;
              _addAccounts(id, accounts, lists[id].blacklist, ListTypes.OperatorRequiringAuthorizationList);
          }
          function addAccountsToBlacklist(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _addAccounts(id, accounts, lists[id].blacklist, ListTypes.OperatorRequiringAuthorizationList);
          }
          function removeAccountFromBlacklist(uint120 id, address account) external onlyListOwner(id) {
              address[] memory accounts = new address[](1);
              accounts[0] = account;
              _removeAccounts(id, accounts, lists[id].blacklist, ListTypes.OperatorRequiringAuthorizationList);
          }
          function removeAccountsFromBlacklist(uint120 id, address[] calldata accounts) external onlyListOwner(id) notZero(accounts.length) {
              _removeAccounts(id, accounts, lists[id].blacklist, ListTypes.OperatorRequiringAuthorizationList);
          } 
          function getBlacklistedAccounts(uint120 id) external view returns (address[] memory) {
              return lists[id].blacklist.enumerableAccounts.values();
          }
          function isAccountBlacklisted(uint120 id, address account) external view returns (bool) {
              return lists[id].blacklist.nonEnumerableAccounts[account];
          }
          function getBlacklistedAccountsByCollection(address collection) external view returns (address[] memory) {
              return lists[collectionConfiguration[collection].listId].blacklist.enumerableAccounts.values();
          }
          function isAccountBlacklistedByCollection(address collection, address account) external view returns (bool) {
              return lists[collectionConfiguration[collection].listId].blacklist.nonEnumerableAccounts[account];
          }
          /// Ensure that a specific operator has been authorized to transfer tokens
          function validateTransfer(address caller, address from, address to) external view {
              _validateTransfer(caller, from, to);
          }
          /// Ensure that a transfer has been authorized for a specific tokenId
          function validateTransfer(address caller, address from, address to, uint256 tokenId) external view {
              _validateTransferByIdentifer(caller, from, to, tokenId);
          }
          /// Ensure that a transfer has been authorized for a specific amount of a specific tokenId, and
          /// reduce the transferable amount remaining
          function validateTransfer(address caller, address from, address to, uint256 tokenId, uint256 amount) external {
              _validateTransferByAmount(caller, from, to, tokenId, amount);
          }
          /// Legacy alias for validateTransfer (address caller, address from, address to)
          function applyCollectionTransferPolicy(address caller, address from, address to) external view {
              _validateTransfer(caller, from, to);
          }
          /// Temporarily assign a specific allowed operator for a given collection
          function beforeAuthorizedTransfer(address operator, address token) external {
              _ensureCallerIsCollectionAuthorizer(token);
              _setTstorish(
                  _getAuthorizedOperatorSlot(token),
                  uint256(uint160(operator))
              );
          }
          /// Clear assignment of a specific allowed operator for a given collection
          function afterAuthorizedTransfer(address token) external {
              _ensureCallerIsCollectionAuthorizer(token);
              _clearTstorish(_getAuthorizedOperatorSlot(token));
          }
          /// Temporarily allow a specific tokenId from a given collection to be transferred
          function beforeAuthorizedTransfer(address token, uint256 tokenId) external {
              _ensureCallerIsCollectionAuthorizer(token);
              _setTstorish(
                  _getAuthorizedIdentifierSlot(token, tokenId),
                  1
              );
          }
          /// Clear assignment of an specific tokenId's transfer allowance
          function afterAuthorizedTransfer(address token, uint256 tokenId) external {
              _ensureCallerIsCollectionAuthorizer(token);
              _clearTstorish(_getAuthorizedIdentifierSlot(token, tokenId));
          }
          /// Temporarily allow a specific amount of a specific tokenId from a given collection to be transferred
          function beforeAuthorizedTransferWithAmount(address token, uint256 tokenId, uint256 amount) external {
              _ensureCallerIsCollectionAuthorizer(token);
              uint256 slot = _getAuthorizedAmountSlot(token, tokenId);
              uint256 currentAmount = _getTstorish(slot);
              uint256 newAmount = currentAmount + amount;
              _setTstorish(slot, newAmount);
          }
          /// Clear assignment of a tokenId's transfer allowance for a specific amount
          function afterAuthorizedTransferWithAmount(address token, uint256 tokenId) external {
              _ensureCallerIsCollectionAuthorizer(token);
              _clearTstorish(_getAuthorizedAmountSlot(token, tokenId));
          }
          function setTransferSecurityLevelOfCollection(
              address collection,
              uint8 level,
              bool enableAuthorizationMode,
              bool authorizersCanSetWildcardOperators,
              bool enableAccountFreezingMode
          ) external {
              if (!enableAuthorizationMode || !authorizersCanSetWildcardOperators || enableAccountFreezingMode) {
                  revert StrictAuthorizedTransferSecurityRegistry__UnsupportedSecurityLevelDetail();
              }
              _setTransferSecurityLevelOfCollection(collection, TransferSecurityLevels(level));
          }
          function setTransferSecurityLevelOfCollection(
              address collection,
              TransferSecurityLevels level
          ) external {
              _setTransferSecurityLevelOfCollection(collection, level);
          }
          function isVerifiedEOA(address account) external view returns (bool) {
              return _EOA_REGISTRY.isVerifiedEOA(account);
          }
          /// @notice ERC-165 Interface Support
          function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) {
              return
                  interfaceId == type(ICreatorTokenTransferValidator).interfaceId ||
                  interfaceId == type(IStrictAuthorizedTransferSecurityRegistry).interfaceId ||
                  super.supportsInterface(interfaceId);
          }
          function _setTransferSecurityLevelOfCollection(
              address collection,
              TransferSecurityLevels level
          ) internal {
              _requireCallerIsNFTOrContractOwnerOrAdmin(collection);
              if (level == TransferSecurityLevels.Recommended) {
                  level = TransferSecurityLevels.Three;
              }
              CollectionConfiguration storage config = collectionConfiguration[collection];
              
              if (level == TransferSecurityLevels.One) {
                  config.policyBypassed = true;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = false;
                  config.contractRecipientsDisabled = false;
                  config.signatureRegistrationRequired = false;
              } else if (level == TransferSecurityLevels.Two) {
                  config.policyBypassed = false;
                  config.blacklistBased = true;
                  config.directTransfersDisabled = false;
                  config.contractRecipientsDisabled = false;
                  config.signatureRegistrationRequired = false;
              } else if (level == TransferSecurityLevels.Three) {
                  config.policyBypassed = false;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = false;
                  config.contractRecipientsDisabled = false;
                  config.signatureRegistrationRequired = false;
              } else if (level == TransferSecurityLevels.Four) {
                  config.policyBypassed = false;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = true;
                  config.contractRecipientsDisabled = false;
                  config.signatureRegistrationRequired = false;
              } else if (level == TransferSecurityLevels.Five) {
                  config.policyBypassed = false;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = false;
                  config.contractRecipientsDisabled = true;
                  config.signatureRegistrationRequired = false;
              } else if (level == TransferSecurityLevels.Six) {
                  config.policyBypassed = false;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = false;
                  config.contractRecipientsDisabled = false;
                  config.signatureRegistrationRequired = true;
              } else if (level == TransferSecurityLevels.Seven) {
                  config.policyBypassed = false;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = true;
                  config.contractRecipientsDisabled = true;
                  config.signatureRegistrationRequired = false;
              } else if (level == TransferSecurityLevels.Eight) {
                  config.policyBypassed = false;
                  config.blacklistBased = false;
                  config.directTransfersDisabled = true;
                  config.contractRecipientsDisabled = false;
                  config.signatureRegistrationRequired = true;
              } else {
                  revert StrictAuthorizedTransferSecurityRegistry__UnsupportedSecurityLevel();
              }
              emit SetTransferSecurityLevel(collection, level);
          }
          /**
           * @notice Copies all addresses in `ptrFromList` to `ptrToList`.
           * 
           * @dev    This function will copy all addresses from one list to another list.
           * @dev    Note: If used to copy adddresses to an existing list the current list contents will not be
           * @dev    deleted before copying. New addresses will be appeneded to the end of the list and the
           * @dev    non-enumerable mapping key value will be set to true.
           * 
           * @dev <h4>Postconditions:</h4>
           *      1. Addresses in from list that are not already present in to list are added to the to list.
           *      2. Emits an `AddedAccountToList` event for each address copied to the list.
           * 
           * @param  listType          The type of list addresses are being copied from and to.
           * @param  destinationListId The id of the list being copied to.
           * @param  ptrFromList       The storage pointer for the list being copied from.
           * @param  ptrToList         The storage pointer for the list being copied to.
           */
          function _copyAddressSet(
              ListTypes listType,
              uint120 destinationListId,
              AccountList storage ptrFromList,
              AccountList storage ptrToList
          ) private {
              EnumerableSet.AddressSet storage ptrFromSet = ptrFromList.enumerableAccounts;
              EnumerableSet.AddressSet storage ptrToSet = ptrToList.enumerableAccounts;
              mapping (address => bool) storage ptrToNonEnumerableSet = ptrToList.nonEnumerableAccounts;
              uint256 sourceLength = ptrFromSet.length();
              address account;
              for (uint256 i = 0; i < sourceLength;) {
                  account = ptrFromSet.at(i); 
                  if (ptrToSet.add(account)) {
                      emit AddedAccountToList(listType, destinationListId, account);
                      ptrToNonEnumerableSet[account] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          /**
           * @notice Requires the caller to be the owner of list `id`.
           * 
           * @dev    Throws when the caller is not the owner of the list.
           */
          function _requireCallerOwnsList(uint120 id) private view {
              if (msg.sender != lists[id].owner) {
                  revert StrictAuthorizedTransferSecurityRegistry__CallerDoesNotOwnList();
              }
          }
          /**
           * @notice Reverts the transaction if the caller is not the owner or assigned the default
           * @notice admin role of the contract at `tokenAddress`.
           *
           * @dev    Throws when the caller is neither owner nor assigned the default admin role.
           * 
           * @param tokenAddress The contract address of the token to check permissions for.
           */
          function _requireCallerIsNFTOrContractOwnerOrAdmin(address tokenAddress) internal view {
              if (msg.sender == tokenAddress) {
                  return;
              }
              if (msg.sender == _safeOwner(tokenAddress)) {
                  return;
              }
              if (!_safeHasRole(tokenAddress)) {
                  revert StrictAuthorizedTransferSecurityRegistry__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
              }
          }
          /**
           * @dev A gas efficient, and fallback-safe way to call the owner function on a token contract.
           *      This will get the owner if it exists - and when the function is unimplemented, the
           *      presence of a fallback function will not result in halted execution.
           */
          function _safeOwner(
              address tokenAddress
          ) internal view returns(address owner) {
              assembly {
                  mstore(0x00, 0x8da5cb5b)
                  let status := staticcall(gas(), tokenAddress, 0x1c, 0x04, 0x00, 0x20)
                  if and(iszero(lt(returndatasize(), 0x20)), status) {
                      owner := mload(0x00)
                  }
              }
          }
          
          /**
           * @dev A gas efficient, and fallback-safe way to call the hasRole function on a token contract.
           *      This will check if the account `hasRole` if `hasRole` exists - and when the function is unimplemented, the
           *      presence of a fallback function will not result in halted execution.
           */
          function _safeHasRole(
              address tokenAddress
          ) internal view returns(bool hasRole) {
              assembly {
                  let ptr := mload(0x40)
                  mstore(0x40, add(ptr, 0x60))
                  mstore(ptr, 0x91d14854)
                  mstore(add(0x20, ptr), DEFAULT_ACCESS_CONTROL_ADMIN_ROLE)
                  mstore(add(0x40, ptr), caller())
                  let status := staticcall(gas(), tokenAddress, add(ptr, 0x1c), 0x44, 0x00, 0x20)
                  if and(iszero(lt(returndatasize(), 0x20)), status) {
                      hasRole := mload(0x00)
                  }
              }
          }
          /**
           * @dev Internal function used to efficiently retrieve the code length of `account`.
           * 
           * @param account The address to get the deployed code length for.
           * 
           * @return length The length of deployed code at the address.
           */
          function _getCodeLengthAsm(address account) internal view returns (uint256 length) {
              assembly { length := extcodesize(account) }
          }
          function _addAccounts(
              uint120 id,
              address[] memory accounts,
              AccountList storage accountList,
              ListTypes listType
          ) internal {
              address account;
              for (uint256 i = 0; i < accounts.length;) {
                  account = accounts[i];
                  if (account == address(0)) {
                      revert StrictAuthorizedTransferSecurityRegistry__ZeroAddressNotAllowed();
                  }
                  if (accountList.enumerableAccounts.add(account)) {
                      emit AddedAccountToList(listType, id, account);
                      accountList.nonEnumerableAccounts[account] = true;
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          function _removeAccounts(
              uint120 id,
              address[] memory accounts,
              AccountList storage accountList,
              ListTypes listType
          ) internal {
              address account;
              for (uint256 i = 0; i < accounts.length;) {
                  account = accounts[i];
                  if (accountList.enumerableAccounts.remove(account)) {
                      emit RemovedAccountFromList(listType, id, account);
                      delete accountList.nonEnumerableAccounts[account];
                  }
                  unchecked {
                      ++i;
                  }
              }
          }
          function _validateTransfer(address operator, address from, address to) internal view {
              CollectionConfiguration memory config = collectionConfiguration[msg.sender];
              if (config.policyBypassed) {
                  return;
              }
              if (config.contractRecipientsDisabled) {
                  if (to.code.length != 0) {
                      revert StrictAuthorizedTransferSecurityRegistry__ReceiverMustNotHaveDeployedCode();
                  }
              }
              
              if (config.signatureRegistrationRequired) {
                  if (!_EOA_REGISTRY.isVerifiedEOA(to)) {
                      revert StrictAuthorizedTransferSecurityRegistry__ReceiverProofOfEOASignatureUnverified();
                  }
              }
              if (operator == from) {
                  if (config.directTransfersDisabled) {
                      revert StrictAuthorizedTransferSecurityRegistry__CallerMustBeWhitelistedOperator();
                  }
                  return;
              }
              uint256 slot = _getAuthorizedOperatorSlot(msg.sender);
              if (operator == address(uint160(_getTstorish(slot)))) {
                  return;
              }
              if (config.blacklistBased) {
                  if (lists[config.listId].blacklist.nonEnumerableAccounts[operator]) {
                      revert StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
                  }
              } else {
                  if (!lists[config.listId].operators.nonEnumerableAccounts[operator]) {
                      revert StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
                  }
              }
          }
          function _validateTransferByIdentifer(address operator, address from, address to, uint256 identifier) internal view {
              CollectionConfiguration memory config = collectionConfiguration[msg.sender];
              if (config.policyBypassed) {
                  return;
              }
              if (config.contractRecipientsDisabled) {
                  if (to.code.length != 0) {
                      revert StrictAuthorizedTransferSecurityRegistry__ReceiverMustNotHaveDeployedCode();
                  }
              }
              
              if (config.signatureRegistrationRequired) {
                  if (!_EOA_REGISTRY.isVerifiedEOA(to)) {
                      revert StrictAuthorizedTransferSecurityRegistry__ReceiverProofOfEOASignatureUnverified();
                  }
              }
              if (operator == from) {
                  if (config.directTransfersDisabled) {
                      revert StrictAuthorizedTransferSecurityRegistry__CallerMustBeWhitelistedOperator();
                  }
                  return;
              }
              uint256 slot = _getAuthorizedIdentifierSlot(msg.sender, identifier);
              uint256 authorizedIdentifier = _getTstorish(slot);
              if (authorizedIdentifier != 0) {
                  return;
              }
              if (config.blacklistBased) {
                  if (lists[config.listId].blacklist.nonEnumerableAccounts[operator]) {
                      revert StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
                  }
              } else {
                  if (!lists[config.listId].operators.nonEnumerableAccounts[operator]) {
                      revert StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
                  }
              }
          }
          function _validateTransferByAmount(address operator, address from, address to, uint256 identifier, uint256 amount) internal {
              CollectionConfiguration memory config = collectionConfiguration[msg.sender];
              if (config.policyBypassed) {
                  return;
              }
              if (config.contractRecipientsDisabled) {
                  if (to.code.length != 0) {
                      revert StrictAuthorizedTransferSecurityRegistry__ReceiverMustNotHaveDeployedCode();
                  }
              }
              
              if (config.signatureRegistrationRequired) {
                  if (!_EOA_REGISTRY.isVerifiedEOA(to)) {
                      revert StrictAuthorizedTransferSecurityRegistry__ReceiverProofOfEOASignatureUnverified();
                  }
              }
              if (operator == from) {
                  if (config.directTransfersDisabled) {
                      revert StrictAuthorizedTransferSecurityRegistry__CallerMustBeWhitelistedOperator();
                  }
                  return;
              }
              uint256 slot = _getAuthorizedAmountSlot(msg.sender, identifier);
              uint256 authorizedAmount = _getTstorish(slot);
              if (authorizedAmount >= amount) {
                  unchecked {
                      _setTstorish(slot, authorizedAmount - amount);
                  }
                  return;
              }
              if (config.blacklistBased) {
                  if (lists[config.listId].blacklist.nonEnumerableAccounts[operator]) {
                      revert StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
                  }
              } else {
                  if (!lists[config.listId].operators.nonEnumerableAccounts[operator]) {
                      revert StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
                  }
              }
          }
          function _getAuthorizedOperatorSlot(
              address collection
          ) internal pure returns (uint256 slot) {
              bytes4 authorizedOperatorScope = _AUTHORIZED_OPERATOR_SCOPE;
              assembly {
                  slot := or(
                      authorizedOperatorScope,
                      and(collection, 0xffffffffffffffffffffffffffffffffffffffff)
                  )
              }
          }
          function _getAuthorizedIdentifierSlot(
              address collection,
              uint256 identifier
          ) internal pure returns (uint256 slot) {
              bytes4 authorizedIdentifierScope = _AUTHORIZED_IDENTIFIER_SCOPE;
              assembly {
                  mstore(0x0, authorizedIdentifierScope)
                  mstore(0x18, collection)
                  mstore(0x04, identifier)
                  slot := keccak256(0x0, 0x38)
              }
          }
          function _getAuthorizedAmountSlot(
              address collection,
              uint256 identifier
          ) internal pure returns (uint256 slot) {
              bytes4 authorizedAmountScope = _AUTHORIZED_AMOUNT_SCOPE;
              assembly {
                  mstore(0x0, authorizedAmountScope)
                  mstore(0x18, collection)
                  mstore(0x04, identifier)
                  slot := keccak256(0x0, 0x38)
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      enum ListTypes {
          AuthorizerList,
          OperatorList,
          OperatorRequiringAuthorizationList
      }
      enum TransferSecurityLevels {
          Recommended,
          One,
          Two,
          Three,
          Four,
          Five,
          Six,
          Seven,
          Eight
      }
      /// @title IStrictAuthorizedTransferSecurityRegistry
      /// @dev Interface for the Authorized Transfer Security Registry, a simplified version of the Transfer
      ///      Security Registry that only supports authorizers and whitelisted operators, and assumes a
      ///      security level of OperatorWhitelistEnableOTC + authorizers for all collections that use it.
      ///      Note that a number of view functions on collections that add this validator will not work.
      interface IStrictAuthorizedTransferSecurityRegistry {
          event CreatedList(uint256 indexed id, string name);
          event AppliedListToCollection(address indexed collection, uint120 indexed id);
          event ReassignedListOwnership(uint256 indexed id, address indexed newOwner);
          event AddedAccountToList(ListTypes indexed kind, uint256 indexed id, address indexed account);
          event RemovedAccountFromList(ListTypes indexed kind, uint256 indexed id, address indexed account);
          event SetTransferSecurityLevel(address collection, TransferSecurityLevels level);
          error StrictAuthorizedTransferSecurityRegistry__ListDoesNotExist();
          error StrictAuthorizedTransferSecurityRegistry__CallerDoesNotOwnList();
          error StrictAuthorizedTransferSecurityRegistry__ArrayLengthCannotBeZero();
          error StrictAuthorizedTransferSecurityRegistry__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
          error StrictAuthorizedTransferSecurityRegistry__ListOwnershipCannotBeTransferredToZeroAddress();
          error StrictAuthorizedTransferSecurityRegistry__ZeroAddressNotAllowed();
          error StrictAuthorizedTransferSecurityRegistry__UnauthorizedTransfer();
          error StrictAuthorizedTransferSecurityRegistry__CallerIsNotValidAuthorizer();
          error StrictAuthorizedTransferSecurityRegistry__UnsupportedSecurityLevel();
          error StrictAuthorizedTransferSecurityRegistry__UnsupportedSecurityLevelDetail();
          error StrictAuthorizedTransferSecurityRegistry__CallerMustBeWhitelistedOperator();
          error StrictAuthorizedTransferSecurityRegistry__ReceiverMustNotHaveDeployedCode();
          error StrictAuthorizedTransferSecurityRegistry__ReceiverProofOfEOASignatureUnverified();
          /// Manage lists of authorizers & operators that can be applied to collections
          function createList(string calldata name) external returns (uint120);
          function createListCopy(string calldata name, uint120 sourceListId) external returns (uint120);
          function reassignOwnershipOfList(uint120 id, address newOwner) external;
          function renounceOwnershipOfList(uint120 id) external;
          function applyListToCollection(address collection, uint120 id) external;
          function listOwners(uint120 id) external view returns (address);
          /// Manage and query for authorizers on lists
          function addAccountToAuthorizers(uint120 id, address account) external;
          function addAccountsToAuthorizers(uint120 id, address[] calldata accounts) external;
          function addAuthorizers(uint120 id, address[] calldata accounts) external;
          function removeAccountFromAuthorizers(uint120 id, address account) external;
          
          function removeAccountsFromAuthorizers(uint120 id, address[] calldata accounts) external;
          function getAuthorizerAccounts(uint120 id) external view returns (address[] memory);
          function isAccountAuthorizer(uint120 id, address account) external view returns (bool);
          function getAuthorizerAccountsByCollection(address collection) external view returns (address[] memory);
          function isAccountAuthorizerOfCollection(address collection, address account) external view returns (bool);
          /// Manage and query for operators on lists
          function addAccountToWhitelist(uint120 id, address account) external;
          function addAccountsToWhitelist(uint120 id, address[] calldata accounts) external;
          function addOperators(uint120 id, address[] calldata accounts) external;
          function removeAccountFromWhitelist(uint120 id, address account) external;
          function removeAccountsFromWhitelist(uint120 id, address[] calldata accounts) external;
          
          function getWhitelistedAccounts(uint120 id) external view returns (address[] memory);
          function isAccountWhitelisted(uint120 id, address account) external view returns (bool);
          function getWhitelistedAccountsByCollection(address collection) external view returns (address[] memory);
          function isAccountWhitelistedByCollection(address collection, address account) external view returns (bool);
          /// Manage and query for blacklists on lists
          function addAccountToBlacklist(uint120 id, address account) external;
          function addAccountsToBlacklist(uint120 id, address[] calldata accounts) external;
          function removeAccountFromBlacklist(uint120 id, address account) external;
          function removeAccountsFromBlacklist(uint120 id, address[] calldata accounts) external;
          function getBlacklistedAccounts(uint120 id) external view returns (address[] memory);
          function isAccountBlacklisted(uint120 id, address account) external view returns (bool);
          function getBlacklistedAccountsByCollection(address collection) external view returns (address[] memory);
          function isAccountBlacklistedByCollection(address collection, address account) external view returns (bool);
          function setTransferSecurityLevelOfCollection(
              address collection,
              uint8 level,
              bool enableAuthorizationMode,
              bool authorizersCanSetWildcardOperators,
              bool enableAccountFreezingMode
          ) external;
          function setTransferSecurityLevelOfCollection(
              address collection,
              TransferSecurityLevels level
          ) external;
          function isVerifiedEOA(address account) external view returns (bool);
          /// Ensure that a specific operator has been authorized to transfer tokens
          function validateTransfer(address caller, address from, address to) external view;
          /// Ensure that a transfer has been authorized for a specific tokenId
          function validateTransfer(address caller, address from, address to, uint256 tokenId) external view;
          /// Ensure that a transfer has been authorized for a specific amount of a specific tokenId, and
          /// reduce the transferable amount remaining
          function validateTransfer(address caller, address from, address to, uint256 tokenId, uint256 amount) external;
          /// Legacy alias for validateTransfer (address caller, address from, address to)
          function applyCollectionTransferPolicy(address caller, address from, address to) external view;
          /// Temporarily assign a specific allowed operator for a given collection
          function beforeAuthorizedTransfer(address operator, address token) external;
          /// Clear assignment of a specific allowed operator for a given collection
          function afterAuthorizedTransfer(address token) external;
          /// Temporarily allow a specific tokenId from a given collection to be transferred
          function beforeAuthorizedTransfer(address token, uint256 tokenId) external;
          /// Clear assignment of an specific tokenId's transfer allowance
          function afterAuthorizedTransfer(address token, uint256 tokenId) external;
          /// Temporarily allow a specific amount of a specific tokenId from a given collection to be transferred
          function beforeAuthorizedTransferWithAmount(address token, uint256 tokenId, uint256 amount) external;
          /// Clear assignment of a tokenId's transfer allowance for a specific amount
          function afterAuthorizedTransferWithAmount(address token, uint256 tokenId) external;
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "./IEOARegistry.sol";
      import "./ITransferSecurityRegistry.sol";
      import "./ITransferValidator.sol";
      interface ICreatorTokenTransferValidator is ITransferSecurityRegistry, ITransferValidator, IEOARegistry {}// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      interface IOwnable {
          function owner() external view returns (address);
      }// SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev External interface of AccessControl declared to support ERC165 detection.
       */
      interface IAccessControl {
          /**
           * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
           *
           * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
           * {RoleAdminChanged} not being emitted signaling this.
           *
           * _Available since v3.1._
           */
          event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
          /**
           * @dev Emitted when `account` is granted `role`.
           *
           * `sender` is the account that originated the contract call, an admin role
           * bearer except when using {AccessControl-_setupRole}.
           */
          event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
          /**
           * @dev Emitted when `account` is revoked `role`.
           *
           * `sender` is the account that originated the contract call:
           *   - if using `revokeRole`, it is the admin role bearer
           *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
           */
          event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
          /**
           * @dev Returns `true` if `account` has been granted `role`.
           */
          function hasRole(bytes32 role, address account) external view returns (bool);
          /**
           * @dev Returns the admin role that controls `role`. See {grantRole} and
           * {revokeRole}.
           *
           * To change a role's admin, use {AccessControl-_setRoleAdmin}.
           */
          function getRoleAdmin(bytes32 role) external view returns (bytes32);
          /**
           * @dev Grants `role` to `account`.
           *
           * If `account` had not been already granted `role`, emits a {RoleGranted}
           * event.
           *
           * Requirements:
           *
           * - the caller must have ``role``'s admin role.
           */
          function grantRole(bytes32 role, address account) external;
          /**
           * @dev Revokes `role` from `account`.
           *
           * If `account` had been granted `role`, emits a {RoleRevoked} event.
           *
           * Requirements:
           *
           * - the caller must have ``role``'s admin role.
           */
          function revokeRole(bytes32 role, address account) external;
          /**
           * @dev Revokes `role` from the calling account.
           *
           * Roles are often managed via {grantRole} and {revokeRole}: this function's
           * purpose is to provide a mechanism for accounts to lose their privileges
           * if they are compromised (such as when a trusted device is misplaced).
           *
           * If the calling account had been granted `role`, emits a {RoleRevoked}
           * event.
           *
           * Requirements:
           *
           * - the caller must be `account`.
           */
          function renounceRole(bytes32 role, address account) external;
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.8.0) (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
      // 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
      pragma solidity ^0.8.24;
      contract Tstorish {
          // Declare a storage variable indicating if TSTORE support has been
          // activated post-deployment.
          bool private _tstoreSupport;
          /*
           * ------------------------------------------------------------------------+
           * Opcode      | Mnemonic         | Stack              | Memory            |
           * ------------------------------------------------------------------------|
           * 60 0x02     | PUSH1 0x02       | 0x02               |                   |
           * 60 0x1e     | PUSH1 0x1e       | 0x1e 0x02          |                   |
           * 61 0x3d5c   | PUSH2 0x3d5c     | 0x3d5c 0x1e 0x02   |                   |
           * 3d          | RETURNDATASIZE   | 0 0x3d5c 0x1e 0x02 |                   |
           *                                                                         |
           * :: store deployed bytecode in memory: (3d) RETURNDATASIZE (5c) TLOAD :: |
           * 52          | MSTORE           | 0x1e 0x02          | [0..0x20): 0x3d5c |
           * f3          | RETURN           |                    | [0..0x20): 0x3d5c |
           * ------------------------------------------------------------------------+
           */
          uint256 constant _TLOAD_TEST_PAYLOAD = 0x6002_601e_613d5c_3d_52_f3;
          uint256 constant _TLOAD_TEST_PAYLOAD_LENGTH = 0x0a;
          uint256 constant _TLOAD_TEST_PAYLOAD_OFFSET = 0x16;
          // Declare an immutable variable to store the tstore test contract address.
          address private immutable _tloadTestContract;
          // Declare an immutable variable to store the initial TSTORE support status.
          bool private immutable _tstoreInitialSupport;
          // Declare an immutable function type variable for the _setTstorish function
          // based on chain support for tstore at time of deployment.
          function(uint256,uint256) internal immutable _setTstorish;
          // Declare an immutable function type variable for the _getTstorish function
          // based on chain support for tstore at time of deployment.
          function(uint256) view returns (uint256) internal immutable _getTstorish;
          // Declare an immutable function type variable for the _clearTstorish function
          // based on chain support for tstore at time of deployment.
          function(uint256) internal immutable _clearTstorish;
          // Declare a few custom revert error types.
          error TStoreAlreadyActivated();
          error TStoreNotSupported();
          error TloadTestContractDeploymentFailed();
          error OnlyDirectCalls();
          /**
           * @dev Determine TSTORE availability during deployment. This involves
           *      attempting to deploy a contract that utilizes TLOAD as part of the
           *      contract construction bytecode, and configuring initial support for
           *      using TSTORE in place of SSTORE based on the result.
           */
          constructor() {
              // Deploy the contract testing TLOAD support and store the address.
              address tloadTestContract = _prepareTloadTest();
              // Ensure the deployment was successful.
              if (tloadTestContract == address(0)) {
                  revert TloadTestContractDeploymentFailed();
              }
              // Determine if TSTORE is supported.
              bool tstoreInitialSupport = _testTload(tloadTestContract);
              if (tstoreInitialSupport) {
                  // If TSTORE is supported, set functions to their versions that use
                  // tstore/tload directly without support checks.
                  _setTstorish = _setTstore;
                  _getTstorish = _getTstore;
                  _clearTstorish = _clearTstore;
              } else {
                  // If TSTORE is not supported, set functions to their versions that 
                  // fallback to sstore/sload until _tstoreSupport is true.
                  _setTstorish = _setTstorishWithSstoreFallback;
                  _getTstorish = _getTstorishWithSloadFallback;
                  _clearTstorish = _clearTstorishWithSstoreFallback;
              }
              _tstoreInitialSupport = tstoreInitialSupport;
              // Set the address of the deployed TLOAD test contract as an immutable.
              _tloadTestContract = tloadTestContract;
          }
          /**
           * @dev External function to activate TSTORE usage. Does not need to be
           *      called if TSTORE is supported from deployment, and only needs to be
           *      called once. Reverts if TSTORE has already been activated or if the
           *      opcode is not available. Note that this must be called directly from
           *      an externally-owned account to avoid potential reentrancy issues.
           */
          function __activateTstore() external {
              // Ensure this function is triggered from an externally-owned account.
              if (msg.sender != tx.origin) {
                  revert OnlyDirectCalls();
              }
              // Determine if TSTORE can potentially be activated.
              if (_tstoreInitialSupport || _tstoreSupport) {
                  revert TStoreAlreadyActivated();
              }
              // Determine if TSTORE can be activated and revert if not.
              if (!_testTload(_tloadTestContract)) {
                  revert TStoreNotSupported();
              }
              // Mark TSTORE as activated.
              _tstoreSupport = true;
          }
          /**
           * @dev Private function to set a TSTORISH value. Assigned to _setTstorish 
           *      internal function variable at construction if chain has tstore support.
           *
           * @param storageSlot The slot to write the TSTORISH value to.
           * @param value       The value to write to the given storage slot.
           */
          function _setTstore(uint256 storageSlot, uint256 value) private {
              assembly {
                  tstore(storageSlot, value)
              }
          }
          /**
           * @dev Private function to set a TSTORISH value with sstore fallback. 
           *      Assigned to _setTstorish internal function variable at construction
           *      if chain does not have tstore support.
           *
           * @param storageSlot The slot to write the TSTORISH value to.
           * @param value       The value to write to the given storage slot.
           */
          function _setTstorishWithSstoreFallback(uint256 storageSlot, uint256 value) private {
              if (_tstoreSupport) {
                  assembly {
                      tstore(storageSlot, value)
                  }
              } else {
                  assembly {
                      sstore(storageSlot, value)
                  }
              }
          }
          /**
           * @dev Private function to read a TSTORISH value. Assigned to _getTstorish
           *      internal function variable at construction if chain has tstore support.
           *
           * @param storageSlot The slot to read the TSTORISH value from.
           *
           * @return value The TSTORISH value at the given storage slot.
           */
          function _getTstore(
              uint256 storageSlot
          ) private view returns (uint256 value) {
              assembly {
                  value := tload(storageSlot)
              }
          }
          /**
           * @dev Private function to read a TSTORISH value with sload fallback. 
           *      Assigned to _getTstorish internal function variable at construction
           *      if chain does not have tstore support.
           *
           * @param storageSlot The slot to read the TSTORISH value from.
           *
           * @return value The TSTORISH value at the given storage slot.
           */
          function _getTstorishWithSloadFallback(
              uint256 storageSlot
          ) private view returns (uint256 value) {
              if (_tstoreSupport) {
                  assembly {
                      value := tload(storageSlot)
                  }
              } else {
                  assembly {
                      value := sload(storageSlot)
                  }
              }
          }
          /**
           * @dev Private function to clear a TSTORISH value. Assigned to _clearTstorish internal 
           *      function variable at construction if chain has tstore support.
           *
           * @param storageSlot The slot to clear the TSTORISH value for.
           */
          function _clearTstore(uint256 storageSlot) private {
              assembly {
                  tstore(storageSlot, 0)
              }
          }
          /**
           * @dev Private function to clear a TSTORISH value with sstore fallback. 
           *      Assigned to _clearTstorish internal function variable at construction
           *      if chain does not have tstore support.
           *
           * @param storageSlot The slot to clear the TSTORISH value for.
           */
          function _clearTstorishWithSstoreFallback(uint256 storageSlot) private {
              if (_tstoreSupport) {
                  assembly {
                      tstore(storageSlot, 0)
                  }
              } else {
                  assembly {
                      sstore(storageSlot, 0)
                  }
              }
          }
          /**
           * @dev Private function to deploy a test contract that utilizes TLOAD as
           *      part of its fallback logic.
           */
          function _prepareTloadTest() private returns (address contractAddress) {
              // Utilize assembly to deploy a contract testing TLOAD support.
              assembly {
                  // Write the contract deployment code payload to scratch space.
                  mstore(0, _TLOAD_TEST_PAYLOAD)
                  // Deploy the contract.
                  contractAddress := create(
                      0,
                      _TLOAD_TEST_PAYLOAD_OFFSET,
                      _TLOAD_TEST_PAYLOAD_LENGTH
                  )
              }
          }
          /**
           * @dev Private view function to determine if TSTORE/TLOAD are supported by
           *      the current EVM implementation by attempting to call the test
           *      contract, which utilizes TLOAD as part of its fallback logic.
           */
          function _testTload(
              address tloadTestContract
          ) private view returns (bool ok) {
              // Call the test contract, which will perform a TLOAD test. If the call
              // does not revert, then TLOAD/TSTORE is supported. Do not forward all
              // available gas, as all forwarded gas will be consumed on revert.
              (ok, ) = tloadTestContract.staticcall{ gas: gasleft() / 10 }("");
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
      interface IEOARegistry is IERC165 {
          function isVerifiedEOA(address account) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.24;
      import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
      import { Tstorish } from "tstorish/Tstorish.sol";
      import { TransferSecurityLevels } from "./interfaces/IStrictAuthorizedTransferSecurityRegistry.sol";
      /// @title StrictAuthorizedTransferSecurityRegistryExtraViewFns
      /// @dev Additional view functions, called by StrictAuthorizedTransferSecurityRegistry
      ///      via delegatecall in the fallback.
      contract StrictAuthorizedTransferSecurityRegistryExtraViewFns is Tstorish {
          using EnumerableSet for EnumerableSet.AddressSet;
          error StrictAuthorizedTransferSecurityRegistry__NotImplemented();
          struct CollectionSecurityPolicy {
              TransferSecurityLevels transferSecurityLevel;
              uint120 operatorWhitelistId;
              uint120 permittedContractReceiversId;
          }
          struct AccountList {
              EnumerableSet.AddressSet enumerableAccounts;
              mapping (address => bool) nonEnumerableAccounts;
          }
          struct List {
              address owner;
              AccountList authorizers;
              AccountList operators;
          }
          struct CollectionConfiguration {
              uint120 listId;
              bool policyBypassed;
              bool blacklistBased;
              bool directTransfersDisabled;
              bool contractRecipientsDisabled;
              bool signatureRegistrationRequired;
          }
          uint120 private UNUSED_lastListId;
          mapping (uint120 => List) private lists;
          /// @dev Mapping of collection addresses to list ids & security policies.
          mapping (address => CollectionConfiguration) private collectionConfiguration;
          // view functions from other transfer security registries, included for completeness
          function getBlacklistedAccounts(uint120) external pure returns (address[] memory) {}
          function getWhitelistedAccounts(uint120 id) external view returns (address[] memory) {
              return lists[id].operators.enumerableAccounts.values();
          }
          function getBlacklistedCodeHashes(uint120) external pure returns (bytes32[] memory) {}
          function getWhitelistedCodeHashes(uint120) external pure returns (bytes32[] memory) {}
          function isAccountBlacklisted(uint120, address) external pure returns (bool) {
              return false;
          }
          function isAccountWhitelisted(uint120 id, address account) external view returns (bool) {
              return lists[id].operators.nonEnumerableAccounts[account];
          }
          function isCodeHashBlacklisted(uint120, bytes32) external pure returns (bool) {
              return false;
          }
          function isCodeHashWhitelisted(uint120, bytes32) external pure returns (bool) {
              return false;
          }
          function getBlacklistedAccountsByCollection(address) external pure returns (address[] memory) {}
          function getWhitelistedAccountsByCollection(address collection) external view returns (address[] memory) {
              return lists[collectionConfiguration[collection].listId].operators.enumerableAccounts.values();
          }
          function getBlacklistedCodeHashesByCollection(address) external pure returns (bytes32[] memory) {}
          function getWhitelistedCodeHashesByCollection(address) external pure returns (bytes32[] memory) {}
          function isAccountBlacklistedByCollection(address, address) external pure returns (bool) {
              return false;
          }
          function isAccountWhitelistedByCollection(
              address collection, address account
          ) external view returns (bool) {
              return lists[collectionConfiguration[collection].listId].operators.nonEnumerableAccounts[account];
          }
          function isCodeHashBlacklistedByCollection(address, bytes32) external pure returns (bool) {
              return false;
          }
          function isCodeHashWhitelistedByCollection(address, bytes32) external pure returns (bool) {
              return false;
          }
          function getCollectionSecurityPolicy(
              address collection
          ) external view returns (CollectionSecurityPolicy memory) {
              CollectionConfiguration memory config = collectionConfiguration[collection];
              return CollectionSecurityPolicy({
                  transferSecurityLevel: _getSecurityLevel(config),
                  operatorWhitelistId: config.listId,
                  permittedContractReceiversId: 0
              });
          }
          function getWhitelistedOperators(uint120 id) external view returns (address[] memory) {
              return lists[id].operators.enumerableAccounts.values();
          }
          function getPermittedContractReceivers(uint120) external pure returns (address[] memory) {}
          function isOperatorWhitelisted(uint120 id, address operator) external view returns (bool) {
              return lists[id].operators.nonEnumerableAccounts[operator];
          }
          function isContractReceiverPermitted(uint120, address) external pure returns (bool) {
              return true;
          }
          function _getSecurityLevel(
              CollectionConfiguration memory config
          ) internal pure returns (TransferSecurityLevels level) {
              bool policyBypassed = config.policyBypassed;
              bool blacklistBased = config.blacklistBased;
              bool directTransfersDisabled = config.directTransfersDisabled;
              bool contractRecipientsDisabled = config.contractRecipientsDisabled;
              bool signatureRegistrationRequired = config.signatureRegistrationRequired;
              if (policyBypassed) {
                  return TransferSecurityLevels.One;
              }
              if (blacklistBased) {
                  return TransferSecurityLevels.Two;
              }
              if (directTransfersDisabled) {
                  if (signatureRegistrationRequired) {
                      return TransferSecurityLevels.Eight;
                  } else if (contractRecipientsDisabled) {
                      return TransferSecurityLevels.Seven;
                  }
                  return TransferSecurityLevels.Four;
              }
              if (signatureRegistrationRequired) {
                  return TransferSecurityLevels.Six;
              } else if (contractRecipientsDisabled) {
                  return TransferSecurityLevels.Five;
              }
              return TransferSecurityLevels.Three;
          }
          fallback() external {
              revert StrictAuthorizedTransferSecurityRegistry__NotImplemented();
          }
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../utils/TransferPolicy.sol";
      interface ITransferSecurityRegistry {
          event AddedToAllowlist(AllowlistTypes indexed kind, uint256 indexed id, address indexed account);
          event CreatedAllowlist(AllowlistTypes indexed kind, uint256 indexed id, string indexed name);
          event ReassignedAllowlistOwnership(AllowlistTypes indexed kind, uint256 indexed id, address indexed newOwner);
          event RemovedFromAllowlist(AllowlistTypes indexed kind, uint256 indexed id, address indexed account);
          event SetAllowlist(AllowlistTypes indexed kind, address indexed collection, uint120 indexed id);
          event SetTransferSecurityLevel(address indexed collection, TransferSecurityLevels level);
          function createOperatorWhitelist(string calldata name) external returns (uint120);
          function createPermittedContractReceiverAllowlist(string calldata name) external returns (uint120);
          function reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) external;
          function reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) external;
          function renounceOwnershipOfOperatorWhitelist(uint120 id) external;
          function renounceOwnershipOfPermittedContractReceiverAllowlist(uint120 id) external;
          function setTransferSecurityLevelOfCollection(address collection, TransferSecurityLevels level) external;
          function setOperatorWhitelistOfCollection(address collection, uint120 id) external;
          function setPermittedContractReceiverAllowlistOfCollection(address collection, uint120 id) external;
          function addOperatorToWhitelist(uint120 id, address operator) external;
          function addPermittedContractReceiverToAllowlist(uint120 id, address receiver) external;
          function removeOperatorFromWhitelist(uint120 id, address operator) external;
          function removePermittedContractReceiverFromAllowlist(uint120 id, address receiver) external;
          function getCollectionSecurityPolicy(address collection) external view returns (CollectionSecurityPolicy memory);
          function getWhitelistedOperators(uint120 id) external view returns (address[] memory);
          function getPermittedContractReceivers(uint120 id) external view returns (address[] memory);
          function isOperatorWhitelisted(uint120 id, address operator) external view returns (bool);
          function isContractReceiverPermitted(uint120 id, address receiver) external view returns (bool);
      }// SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      import "../utils/TransferPolicy.sol";
      interface ITransferValidator {
          function applyCollectionTransferPolicy(address caller, address from, address to) external view;
      }// SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Interface of the ERC165 standard, as defined in the
       * https://eips.ethereum.org/EIPS/eip-165[EIP].
       *
       * Implementers can declare support of contract interfaces, which can then be
       * queried by others ({ERC165Checker}).
       *
       * For an implementation, see {ERC165}.
       */
      interface IERC165 {
          /**
           * @dev Returns true if this contract implements the interface defined by
           * `interfaceId`. See the corresponding
           * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
           * to learn more about how these ids are created.
           *
           * This function call must use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      /** 
       * @dev Used in events to indicate the list type that an account or 
       * @dev codehash is being added to or removed from.
       * 
       * @dev Used in Creator Token Standards V2.
       */
      enum ListTypes {
          // 0: List type that will block a matching address/codehash that is on the list.
          Blacklist,
          // 1: List type that will block any matching address/codehash that is not on the list.
          Whitelist
      }
      /** 
       * @dev Used in events to indicate the list type that event relates to.
       * 
       * @dev Used in Creator Token Standards V1.
       */
      enum AllowlistTypes {
          // 0: List type that defines the allowed operator addresses.
          Operators,
          // 1: List type that defines the allowed contract receivers.
          PermittedContractReceivers
      }
      /**
       @dev Defines the constraints that will be applied for receipt of tokens.
       */
      enum ReceiverConstraints {
          // 0: Any address may receive tokens.
          None,
          // 1: Address must not have deployed bytecode.
          NoCode,
          // 2: Address must verify a signature with the EOA Registry to prove it is an EOA.
          EOA
      }
      /**
       * @dev Defines the constraints that will be applied to the transfer caller.
       */
      enum CallerConstraints {
          // 0: Any address may transfer tokens.
          None,
          // 1: Addresses and codehashes not on the blacklist may transfer tokens.
          OperatorBlacklistEnableOTC,
          // 2: Addresses and codehashes on the whitelist and the owner of the token may transfer tokens.
          OperatorWhitelistEnableOTC,
          // 3: Addresses and codehashes on the whitelist may transfer tokens.
          OperatorWhitelistDisableOTC
      }
      /**
       * @dev Defines constraints for staking tokens in token wrapper contracts.
       */
      enum StakerConstraints {
          // 0: No constraints applied to staker.
          None,
          // 1: Transaction originator must be the address that will receive the wrapped tokens.
          CallerIsTxOrigin,
          // 2: Address that will receive the wrapped tokens must be a verified EOA.
          EOA
      }
      /**
       * @dev Used in both Creator Token Standards V1 and V2.
       * @dev Levels may have different transfer restrictions in V1 and V2. Refer to the 
       * @dev Creator Token Transfer Validator implementation for the version being utilized
       * @dev to determine the effect of the selected level.
       */
      enum TransferSecurityLevels {
          Recommended,
          One,
          Two,
          Three,
          Four,
          Five,
          Six,
          Seven,
          Eight
      }
      /**
       * @dev Defines the caller and receiver constraints for a transfer security level.
       * @dev Used in Creator Token Standards V1.
       * 
       * @dev **callerConstraints**: The restrictions applied to the transfer caller.
       * @dev **receiverConstraints**: The restrictions applied to the transfer recipient.
       */
      struct TransferSecurityPolicy {
          CallerConstraints callerConstraints;
          ReceiverConstraints receiverConstraints;
      }
      /**
       * @dev Defines the security policy for a token collection in Creator Token Standards V1.
       * 
       * @dev **transferSecurityLevel**: The transfer security level set for the collection.
       * @dev **operatorWhitelistId**: The list id for the operator whitelist.
       * @dev **permittedContractReceiversId: The list id for the contracts that are allowed to receive tokens.
       */
      struct CollectionSecurityPolicy {
          TransferSecurityLevels transferSecurityLevel;
          uint120 operatorWhitelistId;
          uint120 permittedContractReceiversId;
      }
      /**
       * @dev Defines the security policy for a token collection in Creator Token Standards V2.
       * 
       * @dev **transferSecurityLevel**: The transfer security level set for the collection.
       * @dev **listId**: The list id that contains the blacklist and whitelist to apply to the collection.
       */
      struct CollectionSecurityPolicyV2 {
          TransferSecurityLevels transferSecurityLevel;
          uint120 listId;
      }
      /** 
       * @dev Used internally in the Creator Token Base V2 contract to pack transfer validator configuration.
       * 
       * @dev **isInitialized**: If not initialized by the collection owner or admin the default validator will be used.
       * @dev **version**: The transfer validator version.
       * @dev **transferValidator**: The address of the transfer validator to use for applying collection security settings.
       */
      struct TransferValidatorReference {
          bool isInitialized;
          uint16 version;
          address transferValidator;
      }